From c0777f4ac7fc6c5df59573afb0c7e0efb7308d40 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 5 Feb 2026 11:50:39 +0100 Subject: [PATCH 01/24] feat: add sleep primitive and update main to test it Add a sleep primitive composed by an asm entry point and a rust function, save the current task context and compute awake tick --- src/arch/riscv32/asm/mod.rs | 2 ++ src/arch/riscv32/asm/sleep.S | 18 ++++++++++++++++++ src/main.rs | 18 +++++++++++++++++- src/primitives/mod.rs | 1 + src/primitives/sleep.rs | 29 +++++++++++++++++++++++++++++ 5 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/arch/riscv32/asm/sleep.S create mode 100644 src/primitives/sleep.rs diff --git a/src/arch/riscv32/asm/mod.rs b/src/arch/riscv32/asm/mod.rs index 431ae31..3922002 100644 --- a/src/arch/riscv32/asm/mod.rs +++ b/src/arch/riscv32/asm/mod.rs @@ -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 diff --git a/src/arch/riscv32/asm/sleep.S b/src/arch/riscv32/asm/sleep.S new file mode 100644 index 0000000..8ebc97c --- /dev/null +++ b/src/arch/riscv32/asm/sleep.S @@ -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 diff --git a/src/main.rs b/src/main.rs index fe4f2a7..fc9bd5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,7 +58,11 @@ pub mod tests; use core::panic::PanicInfo; use logs::LogLevel; use mem::mem_kernel_stack_info; -use primitives::ring_buff::RingBuffer; +use primitives::{ring_buff::RingBuffer, sleep::sleep}; +use task::{ + CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, task_context_switch, + task_create, +}; // Static buffer to use as a ready queue for task. pub static mut BUFFER: RingBuffer = RingBuffer::init(); @@ -74,6 +78,11 @@ unsafe extern "C" fn main() -> ! { kernel_stack.bottom ); log!(LogLevel::Info, "LrnRTOS started!"); + task_create("Test sleep", task_fn, 1, 0x200); + unsafe { CURRENT_TASK_PID = 1 }; + let mut task = task_list_get_task_by_pid(unsafe { CURRENT_TASK_PID }); + unsafe { TASK_HANDLER = *task.as_mut().unwrap() }; + task_context_switch(task.unwrap()); loop { log!(LogLevel::Debug, "Main loop uptime."); unsafe { @@ -82,6 +91,13 @@ unsafe extern "C" fn main() -> ! { } } +fn task_fn() -> ! { + loop { + log!(LogLevel::Debug, "Test sleep task function"); + unsafe { sleep(10) }; + } +} + #[panic_handler] #[cfg(not(feature = "test"))] fn panic_handler(panic: &PanicInfo) -> ! { diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index 0aa67ee..d4c7ad4 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -1,2 +1,3 @@ pub mod ring_buff; +pub mod sleep; pub mod stack; diff --git a/src/primitives/sleep.rs b/src/primitives/sleep.rs new file mode 100644 index 0000000..c21f81e --- /dev/null +++ b/src/primitives/sleep.rs @@ -0,0 +1,29 @@ +use core::ptr::null_mut; + +use crate::{ + ktime::tick::GLOBAL_TICK, + logs::{self, LogLevel}, + task::{TASK_HANDLER, Task}, +}; +use crate::log; + +unsafe extern "C" { + pub fn sleep(tick: usize); +} + +// Use no mangle because this function is called from an asm function +#[unsafe(no_mangle)] +fn task_set_wake_tick(tick: usize) { + let current_task: *mut Task = unsafe { TASK_HANDLER }; + if current_task == null_mut() { + log!( + LogLevel::Error, + "Error getting the current task, invariant violated. Sleep couldn't be used outside of a task." + ); + // See how to handle this, what to return or something else. + } + let current_tick = unsafe { GLOBAL_TICK }; + let awake_tick = current_tick + tick; + // Update task + // Call reschedule or return and continue in sleep fn +} From 5e7a747b60c008f95cd6c637dc39aa73cd781960 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 5 Feb 2026 14:40:41 +0100 Subject: [PATCH 02/24] refactor: move sleep from primitive to task Sleep is more a task public API fn than a primitive type --- src/main.rs | 6 +++--- src/primitives/mod.rs | 1 - src/task/mod.rs | 1 + src/{primitives => task}/sleep.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) rename src/{primitives => task}/sleep.rs (100%) diff --git a/src/main.rs b/src/main.rs index fc9bd5e..5d38e49 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,10 +58,10 @@ pub mod tests; use core::panic::PanicInfo; use logs::LogLevel; use mem::mem_kernel_stack_info; -use primitives::{ring_buff::RingBuffer, sleep::sleep}; +use primitives::ring_buff::RingBuffer; use task::{ - CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, task_context_switch, - task_create, + CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, sleep::sleep, + task_context_switch, task_create, }; // Static buffer to use as a ready queue for task. diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index d4c7ad4..0aa67ee 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -1,3 +1,2 @@ pub mod ring_buff; -pub mod sleep; pub mod stack; diff --git a/src/task/mod.rs b/src/task/mod.rs index 230c779..0937cc4 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -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 sleep; // Mutable static to keep track of the current task // Only relevant on a monocore CPU. diff --git a/src/primitives/sleep.rs b/src/task/sleep.rs similarity index 100% rename from src/primitives/sleep.rs rename to src/task/sleep.rs index c21f81e..1337d94 100644 --- a/src/primitives/sleep.rs +++ b/src/task/sleep.rs @@ -1,11 +1,11 @@ use core::ptr::null_mut; +use crate::log; use crate::{ ktime::tick::GLOBAL_TICK, logs::{self, LogLevel}, task::{TASK_HANDLER, Task}, }; -use crate::log; unsafe extern "C" { pub fn sleep(tick: usize); From cdeb511fc3f8fc1dd459ad4aadb168082a394c2b Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 5 Feb 2026 15:55:31 +0100 Subject: [PATCH 03/24] feat: update the task structure to add a TaskBlockControl field, update dispatch to be aware of that --- src/scheduler/mod.rs | 16 +++++++++------- src/task/mod.rs | 13 ++++++++++++- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index d2c7d73..9bd9589 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -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(¤t_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(¤t_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() }; diff --git a/src/task/mod.rs b/src/task/mod.rs index 0937cc4..0bffdf2 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -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; pub mod sleep; // Mutable static to keep track of the current task @@ -37,21 +38,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, @@ -90,6 +100,7 @@ impl Task { mem_reg.expect("Error: failed to get the task memory region"), func, ), + block_control: TaskBlockControl::None, func, pid: 0, name: buf, From 532b9cc312d0ad32df0a696a6eb8ec9537368c7a Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 5 Feb 2026 15:56:33 +0100 Subject: [PATCH 04/24] feat: add a new task primitive task_block_until and move some logic from sleep to this one --- src/task/primitives.rs | 31 +++++++++++++++++++++++++++++++ src/task/sleep.rs | 25 +++++++++++++------------ 2 files changed, 44 insertions(+), 12 deletions(-) create mode 100644 src/task/primitives.rs diff --git a/src/task/primitives.rs b/src/task/primitives.rs new file mode 100644 index 0000000..dfd7dbc --- /dev/null +++ b/src/task/primitives.rs @@ -0,0 +1,31 @@ +use core::ptr::null_mut; + +use crate::{BLOCKED_QUEUE, log, logs::LogLevel}; + +use super::{ + TASK_HANDLER, Task, TaskBlockControl, TaskState, list::task_list_update_task_by_pid, task_pid, +}; + +pub fn task_block_until(tick: usize) { + let current_task: *mut Task = unsafe { TASK_HANDLER }; + if current_task == null_mut() { + log!( + LogLevel::Error, + "Error getting the current task, invariant violated. Sleep couldn't be used outside of a task." + ); + // See how to handle this, what to return or something else. + } + let mut current_task_deref: Task = unsafe { *current_task }; + // Update task + // Update current state to block + current_task_deref.state = TaskState::Blocked; + // Update block control and pass the awake_tick to it + current_task_deref.block_control = TaskBlockControl::AwakeTick(tick); + // Update task and push pid to block queue + let pid = task_pid(¤t_task_deref); + task_list_update_task_by_pid(pid, current_task_deref); + #[allow(static_mut_refs)] + unsafe { + BLOCKED_QUEUE.push(pid); + } +} diff --git a/src/task/sleep.rs b/src/task/sleep.rs index 1337d94..b0790fd 100644 --- a/src/task/sleep.rs +++ b/src/task/sleep.rs @@ -1,29 +1,30 @@ use core::ptr::null_mut; -use crate::log; +use crate::scheduler::dispatch; +use crate::{BLOCKED_QUEUE, log}; use crate::{ ktime::tick::GLOBAL_TICK, - logs::{self, LogLevel}, + logs::LogLevel, task::{TASK_HANDLER, Task}, }; +use super::list::task_list_update_task_by_pid; +use super::primitives::task_block_until; +use super::{TaskBlockControl, TaskState, task_pid}; + unsafe extern "C" { + // Put the current task to sleep until the number of tick given is passed + // tick: the number of tick the task need to sleep. pub fn sleep(tick: usize); } // Use no mangle because this function is called from an asm function #[unsafe(no_mangle)] fn task_set_wake_tick(tick: usize) { - let current_task: *mut Task = unsafe { TASK_HANDLER }; - if current_task == null_mut() { - log!( - LogLevel::Error, - "Error getting the current task, invariant violated. Sleep couldn't be used outside of a task." - ); - // See how to handle this, what to return or something else. - } let current_tick = unsafe { GLOBAL_TICK }; let awake_tick = current_tick + tick; - // Update task - // Call reschedule or return and continue in sleep fn + // Call task primitive to update current task state + task_block_until(awake_tick); + // Call a re-schedule + dispatch(); } From e57c6c17befc50d87b0f27d422da61a4994f9f34 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 5 Feb 2026 16:39:56 +0100 Subject: [PATCH 05/24] fix: trap_entry override hart id with trap frame --- src/arch/riscv32/asm/trap_entry.S | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/arch/riscv32/asm/trap_entry.S b/src/arch/riscv32/asm/trap_entry.S index 1deabe0..bfddcb9 100644 --- a/src/arch/riscv32/asm/trap_entry.S +++ b/src/arch/riscv32/asm/trap_entry.S @@ -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 From 1082b922f428516833a5e66d14c3b7c53ca58e2c Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 5 Feb 2026 16:42:19 +0100 Subject: [PATCH 06/24] feat: add a new task primitive task_awake_blocked and use it in timer interrupt to awake the blocked task --- src/arch/riscv32/traps/handler.rs | 8 ++++- src/main.rs | 17 ++++++++- src/task/primitives.rs | 60 +++++++++++++++++++++++++++++-- src/task/sleep.rs | 3 +- 4 files changed, 83 insertions(+), 5 deletions(-) diff --git a/src/arch/riscv32/traps/handler.rs b/src/arch/riscv32/traps/handler.rs index effb92b..edab33b 100644 --- a/src/arch/riscv32/traps/handler.rs +++ b/src/arch/riscv32/traps/handler.rs @@ -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; @@ -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); } diff --git a/src/main.rs b/src/main.rs index 5d38e49..d3f78ee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,6 +54,7 @@ pub mod scheduler; pub mod tests; // Use from modules +use arch::{task::r#yield, traps::enable_interrupts}; #[cfg(not(feature = "test"))] use core::panic::PanicInfo; use logs::LogLevel; @@ -66,6 +67,8 @@ use task::{ // Static buffer to use as a ready queue for task. pub static mut BUFFER: RingBuffer = RingBuffer::init(); +// Queue containing all blocked task. +pub static mut BLOCKED_QUEUE: RingBuffer = RingBuffer::init(); #[unsafe(no_mangle)] unsafe extern "C" fn main() -> ! { @@ -79,9 +82,14 @@ unsafe extern "C" fn main() -> ! { ); log!(LogLevel::Info, "LrnRTOS started!"); task_create("Test sleep", task_fn, 1, 0x200); + task_create("Other test task", test_fn, 1, 0x200); unsafe { CURRENT_TASK_PID = 1 }; let mut task = task_list_get_task_by_pid(unsafe { CURRENT_TASK_PID }); unsafe { TASK_HANDLER = *task.as_mut().unwrap() }; + #[allow(static_mut_refs)] + unsafe { + BUFFER.push(2); + } task_context_switch(task.unwrap()); loop { log!(LogLevel::Debug, "Main loop uptime."); @@ -93,11 +101,18 @@ unsafe extern "C" fn main() -> ! { fn task_fn() -> ! { loop { - log!(LogLevel::Debug, "Test sleep task function"); + log!(LogLevel::Debug, "\n\nTest sleep task function\n\n"); unsafe { sleep(10) }; } } +fn test_fn() -> ! { + loop { + log!(LogLevel::Debug, "Always running or ready task"); + unsafe { r#yield() }; + } +} + #[panic_handler] #[cfg(not(feature = "test"))] fn panic_handler(panic: &PanicInfo) -> ! { diff --git a/src/task/primitives.rs b/src/task/primitives.rs index dfd7dbc..0e757f5 100644 --- a/src/task/primitives.rs +++ b/src/task/primitives.rs @@ -1,11 +1,14 @@ use core::ptr::null_mut; -use crate::{BLOCKED_QUEUE, log, logs::LogLevel}; +use crate::{BLOCKED_QUEUE, BUFFER, log, logs::LogLevel}; use super::{ - TASK_HANDLER, Task, TaskBlockControl, TaskState, list::task_list_update_task_by_pid, task_pid, + TASK_HANDLER, Task, TaskBlockControl, TaskState, + list::{task_list_get_task_by_pid, task_list_update_task_by_pid}, + task_pid, }; +/// Block the current task until the given tick is reach. pub fn task_block_until(tick: usize) { let current_task: *mut Task = unsafe { TASK_HANDLER }; if current_task == null_mut() { @@ -29,3 +32,56 @@ pub fn task_block_until(tick: usize) { BLOCKED_QUEUE.push(pid); } } + +/// Pop the oldest element in the blocked queue and check if the task can be awake. If not, repush +/// it to the blocked queue +/// TODO: Use a better data structure than a RingBuffer for the blocked queue. +pub fn task_awake_blocked(tick: usize) { + #[allow(static_mut_refs)] + let pid = unsafe { BLOCKED_QUEUE.pop() }; + if pid.is_none() { + log!(LogLevel::Error, "Error getting the oldest pid in run queue"); + return; + } + let task = task_list_get_task_by_pid(pid.expect("Error getting the pid behind the Option<>")); + if task.is_none() { + log!( + LogLevel::Error, + "Error getting the task by pid, the task may not exist" + ); + return; + } + // Allow expected, we check the value before, if it's some, there's shouldn't be any problem by + // unwrapping it. + #[allow(clippy::expect_used)] + match task + .expect("Failed to get the task behind the Option<>. This shouldn't be possible") + .block_control + { + TaskBlockControl::AwakeTick(awake_tick) => { + log!( + LogLevel::Debug, + "\n\nHERE IT SHOULD AWAKE: awake_tick: {}\ttick: {}\n\n", + awake_tick, + tick + ); + if tick >= awake_tick { + // push to run queue + log!(LogLevel::Debug, "\n\nHERE IT SHOULD AWAKE\n\n"); + #[allow(static_mut_refs)] + unsafe { + BUFFER.push(pid.expect("Failed to get the pid behind the Option<>")); + }; + return; + } else { + // push to blocked queue + #[allow(static_mut_refs)] + unsafe { + BLOCKED_QUEUE.push(pid.expect("Failed to get the pid behind the Option<>")) + }; + return; + } + } + TaskBlockControl::None => return, + } +} diff --git a/src/task/sleep.rs b/src/task/sleep.rs index b0790fd..6f8b0b7 100644 --- a/src/task/sleep.rs +++ b/src/task/sleep.rs @@ -1,5 +1,6 @@ use core::ptr::null_mut; +use crate::ktime::tick::get_tick; use crate::scheduler::dispatch; use crate::{BLOCKED_QUEUE, log}; use crate::{ @@ -21,7 +22,7 @@ unsafe extern "C" { // Use no mangle because this function is called from an asm function #[unsafe(no_mangle)] fn task_set_wake_tick(tick: usize) { - let current_tick = unsafe { GLOBAL_TICK }; + let current_tick = get_tick(); let awake_tick = current_tick + tick; // Call task primitive to update current task state task_block_until(awake_tick); From 28c894dad9358e614221746a805798b4f9aaadc3 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 5 Feb 2026 16:42:43 +0100 Subject: [PATCH 07/24] chore: format files --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f3833d1..f58b703 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,11 +4,11 @@ version = "0.4.2" 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"] From 79da14f8db6e1d421483f34847cf68983031334c Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Fri, 6 Feb 2026 09:03:47 +0100 Subject: [PATCH 08/24] feat: add the delay task primitive, improve the task_awake primitive to avoid log flooding --- src/main.rs | 8 +++++--- src/task/primitives.rs | 23 +++++++++++++++-------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/main.rs b/src/main.rs index d3f78ee..b280956 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,8 +61,8 @@ use logs::LogLevel; use mem::mem_kernel_stack_info; use primitives::ring_buff::RingBuffer; use task::{ - CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, sleep::sleep, - task_context_switch, task_create, + CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, primitives::delay, + sleep::sleep, task_context_switch, task_create, }; // Static buffer to use as a ready queue for task. @@ -101,7 +101,8 @@ unsafe extern "C" fn main() -> ! { fn task_fn() -> ! { loop { - log!(LogLevel::Debug, "\n\nTest sleep task function\n\n"); + log!(LogLevel::Debug, "Test sleep task function"); + unsafe { r#yield() }; unsafe { sleep(10) }; } } @@ -110,6 +111,7 @@ fn test_fn() -> ! { loop { log!(LogLevel::Debug, "Always running or ready task"); unsafe { r#yield() }; + delay(1000); } } diff --git a/src/task/primitives.rs b/src/task/primitives.rs index 0e757f5..32b1e7d 100644 --- a/src/task/primitives.rs +++ b/src/task/primitives.rs @@ -1,6 +1,9 @@ use core::ptr::null_mut; -use crate::{BLOCKED_QUEUE, BUFFER, log, logs::LogLevel}; +use crate::{ + BLOCKED_QUEUE, BUFFER, arch::traps::interrupt::enable_and_halt, ktime::set_ktime_ms, log, + logs::LogLevel, +}; use super::{ TASK_HANDLER, Task, TaskBlockControl, TaskState, @@ -37,6 +40,11 @@ pub fn task_block_until(tick: usize) { /// it to the blocked queue /// TODO: Use a better data structure than a RingBuffer for the blocked queue. pub fn task_awake_blocked(tick: usize) { + #[allow(static_mut_refs)] + let size = unsafe { BLOCKED_QUEUE.size() }; + if size == 0 { + return; + } #[allow(static_mut_refs)] let pid = unsafe { BLOCKED_QUEUE.pop() }; if pid.is_none() { @@ -59,15 +67,8 @@ pub fn task_awake_blocked(tick: usize) { .block_control { TaskBlockControl::AwakeTick(awake_tick) => { - log!( - LogLevel::Debug, - "\n\nHERE IT SHOULD AWAKE: awake_tick: {}\ttick: {}\n\n", - awake_tick, - tick - ); if tick >= awake_tick { // push to run queue - log!(LogLevel::Debug, "\n\nHERE IT SHOULD AWAKE\n\n"); #[allow(static_mut_refs)] unsafe { BUFFER.push(pid.expect("Failed to get the pid behind the Option<>")); @@ -85,3 +86,9 @@ pub fn task_awake_blocked(tick: usize) { TaskBlockControl::None => return, } } + +/// Interrupt all operation on the CPU for the given time. +pub fn delay(ms: usize) { + set_ktime_ms(ms as u64); + unsafe { enable_and_halt() }; +} From 33a83eac69228b982124885fa991477331065caa Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Fri, 6 Feb 2026 09:08:46 +0100 Subject: [PATCH 09/24] chore: remove unused import --- src/main.rs | 2 +- src/task/sleep.rs | 13 +------------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/main.rs b/src/main.rs index b280956..672c393 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,7 +54,7 @@ pub mod scheduler; pub mod tests; // Use from modules -use arch::{task::r#yield, traps::enable_interrupts}; +use arch::task::r#yield; #[cfg(not(feature = "test"))] use core::panic::PanicInfo; use logs::LogLevel; diff --git a/src/task/sleep.rs b/src/task/sleep.rs index 6f8b0b7..52803e7 100644 --- a/src/task/sleep.rs +++ b/src/task/sleep.rs @@ -1,17 +1,6 @@ -use core::ptr::null_mut; - +use super::primitives::task_block_until; use crate::ktime::tick::get_tick; use crate::scheduler::dispatch; -use crate::{BLOCKED_QUEUE, log}; -use crate::{ - ktime::tick::GLOBAL_TICK, - logs::LogLevel, - task::{TASK_HANDLER, Task}, -}; - -use super::list::task_list_update_task_by_pid; -use super::primitives::task_block_until; -use super::{TaskBlockControl, TaskState, task_pid}; unsafe extern "C" { // Put the current task to sleep until the number of tick given is passed From bddcad74e549b8da146c2aaf8a26a5a67481b598 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Fri, 6 Feb 2026 09:48:30 +0100 Subject: [PATCH 10/24] refactor: move the task sleep primitive function from sleep module to task primitives module --- src/arch/riscv32/task/mod.rs | 2 -- src/main.rs | 4 +--- src/task/mod.rs | 1 - src/task/primitives.rs | 26 +++++++++++++++++++++++++- src/task/sleep.rs | 20 -------------------- 5 files changed, 26 insertions(+), 27 deletions(-) delete mode 100644 src/task/sleep.rs diff --git a/src/arch/riscv32/task/mod.rs b/src/arch/riscv32/task/mod.rs index f227d84..b4916f1 100644 --- a/src/arch/riscv32/task/mod.rs +++ b/src/arch/riscv32/task/mod.rs @@ -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 diff --git a/src/main.rs b/src/main.rs index 672c393..544ba4c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,15 +54,13 @@ pub mod scheduler; pub mod tests; // Use from modules -use arch::task::r#yield; #[cfg(not(feature = "test"))] use core::panic::PanicInfo; use logs::LogLevel; use mem::mem_kernel_stack_info; use primitives::ring_buff::RingBuffer; use task::{ - CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, primitives::delay, - sleep::sleep, task_context_switch, task_create, + list::task_list_get_task_by_pid, primitives::{delay, sleep, r#yield}, task_context_switch, task_create, CURRENT_TASK_PID, TASK_HANDLER }; // Static buffer to use as a ready queue for task. diff --git a/src/task/mod.rs b/src/task/mod.rs index 0bffdf2..27b1bb5 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -23,7 +23,6 @@ use crate::{arch::task::task_context::TaskContext, log, logs::LogLevel, mem::mem pub mod list; pub mod primitives; -pub mod sleep; // Mutable static to keep track of the current task // Only relevant on a monocore CPU. diff --git a/src/task/primitives.rs b/src/task/primitives.rs index 32b1e7d..b90458f 100644 --- a/src/task/primitives.rs +++ b/src/task/primitives.rs @@ -1,8 +1,12 @@ use core::ptr::null_mut; use crate::{ - BLOCKED_QUEUE, BUFFER, arch::traps::interrupt::enable_and_halt, ktime::set_ktime_ms, log, + BLOCKED_QUEUE, BUFFER, + arch::traps::interrupt::enable_and_halt, + ktime::{set_ktime_ms, tick::get_tick}, + log, logs::LogLevel, + scheduler::dispatch, }; use super::{ @@ -11,6 +15,26 @@ use super::{ task_pid, }; +unsafe extern "C" { + // Put the current task to sleep until the number of tick given is passed + // tick: the number of tick the task need to sleep. + pub fn sleep(tick: usize); + // Yield function for cooperative scheduling + pub fn r#yield(); +} + +// Use no mangle because this function is called from an asm function +// Called from sleep primitive +#[unsafe(no_mangle)] +fn task_set_wake_tick(tick: usize) { + let current_tick = get_tick(); + let awake_tick = current_tick + tick; + // Call task primitive to update current task state + task_block_until(awake_tick); + // Call a re-schedule + dispatch(); +} + /// Block the current task until the given tick is reach. pub fn task_block_until(tick: usize) { let current_task: *mut Task = unsafe { TASK_HANDLER }; diff --git a/src/task/sleep.rs b/src/task/sleep.rs deleted file mode 100644 index 52803e7..0000000 --- a/src/task/sleep.rs +++ /dev/null @@ -1,20 +0,0 @@ -use super::primitives::task_block_until; -use crate::ktime::tick::get_tick; -use crate::scheduler::dispatch; - -unsafe extern "C" { - // Put the current task to sleep until the number of tick given is passed - // tick: the number of tick the task need to sleep. - pub fn sleep(tick: usize); -} - -// Use no mangle because this function is called from an asm function -#[unsafe(no_mangle)] -fn task_set_wake_tick(tick: usize) { - let current_tick = get_tick(); - let awake_tick = current_tick + tick; - // Call task primitive to update current task state - task_block_until(awake_tick); - // Call a re-schedule - dispatch(); -} From 5e840062e7f4fbc503a40d442dffa4ba2e8b89bf Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Fri, 6 Feb 2026 09:49:37 +0100 Subject: [PATCH 11/24] docs(kernel): add a basic primitives documentation Add a small description, and very trivial documentation on all current primitives types. If I haven't forgot any. --- Documentation/kernel/primitives.md | 54 ++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 Documentation/kernel/primitives.md diff --git a/Documentation/kernel/primitives.md b/Documentation/kernel/primitives.md new file mode 100644 index 0000000..1119fe3 --- /dev/null +++ b/Documentation/kernel/primitives.md @@ -0,0 +1,54 @@ +# Kernel primitives types and functions + +## Description + +The kernel has multiple primitive to improve the codebase, and qol(quality of life) for developers. +It goes from primitive type like circular buffer, to more specific primitive function like sleep for a task. + +### Primitive type + +#### Description + +The lowest primitive type are the Rust types, but beside them there's the kernel primitive type. +Those can be used anywhere in the kernel. Here's a list of the primitive type currently available in 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. + +#### 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`. + +### 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. + +#### delay + +Block the CPU for the given time, in `ms`. +This is not really recommanded 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`. From 0dc1c1185bb10ca626a82bcce90f74e11e71f434 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Fri, 6 Feb 2026 09:55:42 +0100 Subject: [PATCH 12/24] docs(kernel): add table of content to the primitives docs --- Documentation/kernel/primitives.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Documentation/kernel/primitives.md b/Documentation/kernel/primitives.md index 1119fe3..2ab1079 100644 --- a/Documentation/kernel/primitives.md +++ b/Documentation/kernel/primitives.md @@ -1,5 +1,20 @@ # Kernel primitives types and functions + +- [Kernel primitives types and functions](#kernel-primitives-types-and-functions) + - [Description](#description) + - [Primitive type](#primitive-type) + - [Description](#description-1) + - [RingBuffer](#ringbuffer) + - [AlignedStack16](#alignedstack16) + - [Task primitive](#task-primitive) + - [Description](#description-2) + - [yield](#yield) + - [sleep](#sleep) + - [task_awake_blocked](#taskawakeblocked) + - [delay](#delay) + + ## Description The kernel has multiple primitive to improve the codebase, and qol(quality of life) for developers. @@ -50,5 +65,5 @@ The primitive will get the `oldest blocked task`, from the `BLOCKED_QUEUE`, then #### delay Block the CPU for the given time, in `ms`. -This is not really recommanded to use, it will not put the CPU to sleep, just waiting for the next timer interrupt. +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`. From cdb84ae796ed874d5adc94a2be72f7774b2762b9 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Fri, 6 Feb 2026 14:13:52 +0100 Subject: [PATCH 13/24] feat(tests): add beginning of task primitives test Not working, driving me crazy --- src/tests/suites.rs | 3 +- src/tests/task/mod.rs | 1 + src/tests/task/primitives.rs | 65 ++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/tests/task/primitives.rs diff --git a/src/tests/suites.rs b/src/tests/suites.rs index 86fb8ad..58c5312 100644 --- a/src/tests/suites.rs +++ b/src/tests/suites.rs @@ -15,7 +15,7 @@ use super::{ mem::memory_test_suite, platform::platform_test_suite, primitives::ring_buff::ring_buff_primitive_test_suite, - task::{list::task_list_test_suite, task_test_suite}, + task::{list::task_list_test_suite, primitives::task_primitives_test_suite, task_test_suite}, }; // Call all test suite function to auto register all suites in test manager. @@ -35,4 +35,5 @@ pub fn test_suites() { task_list_test_suite(); task_test_suite(); task_context_test_suite(); + task_primitives_test_suite(); } diff --git a/src/tests/task/mod.rs b/src/tests/task/mod.rs index a11cbe5..a476400 100644 --- a/src/tests/task/mod.rs +++ b/src/tests/task/mod.rs @@ -5,6 +5,7 @@ use crate::{ }; pub mod list; +pub mod primitives; /// This function is only used to create task for testing purpose. /// This must never be used in other cases diff --git a/src/tests/task/primitives.rs b/src/tests/task/primitives.rs new file mode 100644 index 0000000..da92dbd --- /dev/null +++ b/src/tests/task/primitives.rs @@ -0,0 +1,65 @@ +use crate::{ + arch::traps::interrupt::enable_and_halt, + arch::traps::{disable_interrupts, enable_interrupts, trap_frame::init_trap_frame}, + config::TICK_SAFETY_DURATION, + drivers::timer::TIMER_SUBSYSTEM, + ktime::{set_ktime_ms, tick::get_tick}, + print, + task::{ + CURRENT_TASK_PID, TASK_HANDLER, + list::task_list_get_task_by_pid, + primitives::{delay, r#yield}, + task_context_switch, task_create, + }, + test_failed, test_info, + tests::{TEST_MANAGER, TestBehavior, TestCase, TestSuite, TestSuiteBehavior}, +}; +use core::ptr; + +fn task_fn() -> ! { + let mut i: usize = 0; + loop { + print!("delay: {i}\n"); + delay(1000); + if i >= 8 { + // Exit Qemu + unsafe { ptr::write_volatile(0x100000 as *mut u32, 0x5555) }; + } + i += 1; + } +} + +#[unsafe(no_mangle)] +fn test_task_primitives_delay() -> u8 { + task_create("Test delay", task_fn, 1, 0x1000); + unsafe { CURRENT_TASK_PID = 2 }; + let mut task = task_list_get_task_by_pid(unsafe { CURRENT_TASK_PID }); + unsafe { TASK_HANDLER = *task.as_mut().unwrap() }; + test_info!( + "The next output should be the task 'Test delay' printing an integer. The final output should be: 'delay: '" + ); + let sub = TIMER_SUBSYSTEM.get_primary_timer(); + print!("debug: {:?}\n", sub.timer_type); + // loop { + // print!("delay\n"); + // unsafe { enable_and_halt() }; + // } + // task_context_switch(task.unwrap()); + 0 +} + +pub fn task_primitives_test_suite() { + const TASK_PRIMITIVES_TEST_SUITE: TestSuite = TestSuite { + tests: &[TestCase::init( + "Task primitive delay", + test_task_primitives_delay, + TestBehavior::Default, + )], + name: "Task primitives", + behavior: TestSuiteBehavior::Default, + }; + #[allow(static_mut_refs)] + unsafe { + TEST_MANAGER.add_suite(&TASK_PRIMITIVES_TEST_SUITE) + }; +} From 5f2b81b736e56f459593b4d9f47b40d94a688191 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 09:07:22 +0100 Subject: [PATCH 14/24] feat(linkers): update test mode linker ROM length to compile The ROM is not really used for now in test mode so it's ok I guess --- linkers/linker_test_mode.ld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linkers/linker_test_mode.ld b/linkers/linker_test_mode.ld index 3663103..ec770c3 100644 --- a/linkers/linker_test_mode.ld +++ b/linkers/linker_test_mode.ld @@ -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 { From ca2ebdbf596f2719d2df8d97ed6c4e1d3a1db56f Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 09:08:49 +0100 Subject: [PATCH 15/24] feat(tests): add sleep test Testing the invariants from sleep primitive, from the blocked queue and the timer interrupt --- src/tests/task/primitives.rs | 100 ++++++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 19 deletions(-) diff --git a/src/tests/task/primitives.rs b/src/tests/task/primitives.rs index da92dbd..b06833d 100644 --- a/src/tests/task/primitives.rs +++ b/src/tests/task/primitives.rs @@ -1,14 +1,18 @@ use crate::{ - arch::traps::interrupt::enable_and_halt, - arch::traps::{disable_interrupts, enable_interrupts, trap_frame::init_trap_frame}, + BLOCKED_QUEUE, BUFFER, + arch::traps::{ + disable_interrupts, enable_interrupts, + handler::trap_handler, + interrupt::enable_and_halt, + trap_frame::{TrapFrame, init_trap_frame}, + }, config::TICK_SAFETY_DURATION, - drivers::timer::TIMER_SUBSYSTEM, - ktime::{set_ktime_ms, tick::get_tick}, - print, + kprint, + ktime::{set_ktime_ms, set_ktime_seconds, tick::get_tick}, task::{ CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, - primitives::{delay, r#yield}, + primitives::{delay, sleep, r#yield}, task_context_switch, task_create, }, test_failed, test_info, @@ -19,7 +23,7 @@ use core::ptr; fn task_fn() -> ! { let mut i: usize = 0; loop { - print!("delay: {i}\n"); + kprint!("delay\n"); delay(1000); if i >= 8 { // Exit Qemu @@ -29,6 +33,45 @@ fn task_fn() -> ! { } } +fn task_sleep_fn() -> ! { + loop { + unsafe { + sleep(2); + } + } +} + +fn task_testing_sleep() -> ! { + let cause: usize = 2147483655; + // Random mepc + // TODO: improve mepc security in trap handler + let mepc: usize = 125696; + let current_tick = get_tick(); + let mut trap_frame = TrapFrame::init(); + unsafe { trap_handler(mepc, 0, cause, 0, 0, &mut trap_frame) }; + // Blocked queue should have been updated, checking it + #[allow(static_mut_refs)] + let blocked_queue = unsafe { &BLOCKED_QUEUE }; + if blocked_queue.size() != 1 { + test_failed!("The block queue should have one task in it."); + // Use infinite loop to make the CI crash from timeout. Can't return test failed from + // here. + loop {} + } + unsafe { trap_handler(mepc, 0, cause, 0, 0, &mut trap_frame) }; + if blocked_queue.size() != 0 { + test_failed!("The block queue should be empty."); + // Use infinite loop to make the CI crash from timeout. Can't return test failed from + // here. + loop {} + } + test_info!("Invariant from sleep and blocked queue successfully respected. Exit qemu..."); + unsafe { ptr::write_volatile(0x100000 as *mut u32, 0x5555) }; + // Check with condition the invariant, blocked queue updated etc + // Recall trap handler, then check that the block queue is empty. + loop {} +} + #[unsafe(no_mangle)] fn test_task_primitives_delay() -> u8 { task_create("Test delay", task_fn, 1, 0x1000); @@ -38,23 +81,42 @@ fn test_task_primitives_delay() -> u8 { test_info!( "The next output should be the task 'Test delay' printing an integer. The final output should be: 'delay: '" ); - let sub = TIMER_SUBSYSTEM.get_primary_timer(); - print!("debug: {:?}\n", sub.timer_type); - // loop { - // print!("delay\n"); - // unsafe { enable_and_halt() }; - // } - // task_context_switch(task.unwrap()); + set_ktime_seconds(TICK_SAFETY_DURATION); + enable_interrupts(); + task_context_switch(task.unwrap()); + 0 +} + +fn test_task_primitives_sleep() -> u8 { + // pid 2 + task_create("Test sleep", task_sleep_fn, 1, 0x1000); + // pid 3 + task_create("Test sleep invariants", task_testing_sleep, 1, 0x1000); + unsafe { CURRENT_TASK_PID = 2 }; + let mut task = task_list_get_task_by_pid(unsafe { CURRENT_TASK_PID }); + unsafe { TASK_HANDLER = *task.as_mut().unwrap() }; + #[allow(static_mut_refs)] + unsafe { + BUFFER.push(3); + } + task_context_switch(task.unwrap()); 0 } pub fn task_primitives_test_suite() { const TASK_PRIMITIVES_TEST_SUITE: TestSuite = TestSuite { - tests: &[TestCase::init( - "Task primitive delay", - test_task_primitives_delay, - TestBehavior::Default, - )], + tests: &[ + TestCase::init( + "Task primitive delay", + test_task_primitives_delay, + TestBehavior::Skipped, + ), + TestCase::init( + "Task primitive sleep", + test_task_primitives_sleep, + TestBehavior::Default, + ), + ], name: "Task primitives", behavior: TestSuiteBehavior::Default, }; From 1e45b48aa16f7dc2024d4af6e437db3202ffd0bf Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 09:09:15 +0100 Subject: [PATCH 16/24] chore: format file --- src/tests/arch/riscv32/task/task_context.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/tests/arch/riscv32/task/task_context.rs b/src/tests/arch/riscv32/task/task_context.rs index 6e63da1..33e3ad3 100644 --- a/src/tests/arch/riscv32/task/task_context.rs +++ b/src/tests/arch/riscv32/task/task_context.rs @@ -2,15 +2,11 @@ use crate::{BUFFER, print}; use core::{mem, ptr}; use crate::{ - arch::{ - scheduler::init_sched_ctx, - task::{task_context::TaskContext, r#yield}, - traps::interrupt::halt, - }, + arch::{scheduler::init_sched_ctx, task::task_context::TaskContext, traps::interrupt::halt}, scheduler::dispatch, task::{ - CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, task_context_switch, - task_create, + CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, primitives::r#yield, + task_context_switch, task_create, }, test_failed, test_info, tests::{TEST_MANAGER, TestBehavior, TestCase, TestSuite, TestSuiteBehavior}, From 7699c4351e5880b2491b871623b21d1c237ebbcc Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 09:09:48 +0100 Subject: [PATCH 17/24] feat: remove init_trap_frame from a test, and comment the yield test exemple in main --- src/main.rs | 2 +- src/tests/arch/riscv32/traps/interrupt.rs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 544ba4c..92a5f43 100644 --- a/src/main.rs +++ b/src/main.rs @@ -100,7 +100,7 @@ unsafe extern "C" fn main() -> ! { fn task_fn() -> ! { loop { log!(LogLevel::Debug, "Test sleep task function"); - unsafe { r#yield() }; + // unsafe { r#yield() }; unsafe { sleep(10) }; } } diff --git a/src/tests/arch/riscv32/traps/interrupt.rs b/src/tests/arch/riscv32/traps/interrupt.rs index c7dc1ef..9b9c431 100644 --- a/src/tests/arch/riscv32/traps/interrupt.rs +++ b/src/tests/arch/riscv32/traps/interrupt.rs @@ -45,8 +45,6 @@ pub fn test_mtvec_trap_entry() -> u8 { } pub fn test_mscratch_trap_frame() -> u8 { - // Init trap_frame and declare ptr to it - init_trap_frame(); #[allow(static_mut_refs)] // Ptr to KERNEL_TRAP_FRAME static let ptr = unsafe { &mut KERNEL_TRAP_FRAME } as *mut _ as u32; From 48f338228f2acc195e33e92a87d2303bb539664b Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 09:35:37 +0100 Subject: [PATCH 18/24] feat(tests): improve sleep test Add run queue check for invariants --- src/tests/task/primitives.rs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/tests/task/primitives.rs b/src/tests/task/primitives.rs index b06833d..171d5e3 100644 --- a/src/tests/task/primitives.rs +++ b/src/tests/task/primitives.rs @@ -52,15 +52,38 @@ fn task_testing_sleep() -> ! { // Blocked queue should have been updated, checking it #[allow(static_mut_refs)] let blocked_queue = unsafe { &BLOCKED_QUEUE }; + #[allow(static_mut_refs)] + let run_queue = unsafe { &BUFFER }; + if run_queue.size() != 0 { + test_failed!("The run queue should be empty, got: {}", run_queue.size()); + // Use infinite loop to make the CI crash from timeout. Can't return test failed from + // here. + loop {} + } if blocked_queue.size() != 1 { - test_failed!("The block queue should have one task in it."); + test_failed!( + "The block queue should have 1 task in it, got: {}", + blocked_queue.size() + ); // Use infinite loop to make the CI crash from timeout. Can't return test failed from // here. loop {} } unsafe { trap_handler(mepc, 0, cause, 0, 0, &mut trap_frame) }; if blocked_queue.size() != 0 { - test_failed!("The block queue should be empty."); + test_failed!( + "The block queue should be empty, got: {}", + blocked_queue.size() + ); + // Use infinite loop to make the CI crash from timeout. Can't return test failed from + // here. + loop {} + } + if run_queue.size() != 1 { + test_failed!( + "The run queue should have 1 task in it, got: {}", + run_queue.size() + ); // Use infinite loop to make the CI crash from timeout. Can't return test failed from // here. loop {} From f841d2ac4192e241181f300d63c06b67dabfed84 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 10:47:10 +0100 Subject: [PATCH 19/24] feat(docs): update the primitives documentation and add data_structure and timing_helpers to documentation Split the primitives documentation to have more separate logic documentation, data structure and timing helpers are not primitives so there's should not be in the primitives documentation. --- Documentation/kernel/data_structure.md | 39 ++++++++++++++++++++ Documentation/kernel/primitives.md | 49 +++++++++++++++----------- Documentation/kernel/timing_helpers.md | 22 ++++++++++++ 3 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 Documentation/kernel/data_structure.md create mode 100644 Documentation/kernel/timing_helpers.md diff --git a/Documentation/kernel/data_structure.md b/Documentation/kernel/data_structure.md new file mode 100644 index 0000000..c306dd9 --- /dev/null +++ b/Documentation/kernel/data_structure.md @@ -0,0 +1,39 @@ +# Kernel data structure + + +- [Kernel data structure](#kernel-data-structure) + - [Description](#description) + - [RingBuffer](#ringbuffer) + - [Invariants](#invariants) + - [AlignedStack16](#alignedstack16) + - [Invariants](#invariants-1) + + +## 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. diff --git a/Documentation/kernel/primitives.md b/Documentation/kernel/primitives.md index 2ab1079..aaf7412 100644 --- a/Documentation/kernel/primitives.md +++ b/Documentation/kernel/primitives.md @@ -5,37 +5,45 @@ - [Description](#description) - [Primitive type](#primitive-type) - [Description](#description-1) - - [RingBuffer](#ringbuffer) - - [AlignedStack16](#alignedstack16) - [Task primitive](#task-primitive) - [Description](#description-2) - [yield](#yield) - [sleep](#sleep) - [task_awake_blocked](#taskawakeblocked) - - [delay](#delay) + - [Invariants](#invariants) ## Description -The kernel has multiple primitive to improve the codebase, and qol(quality of life) for developers. -It goes from primitive type like circular buffer, to more specific primitive function like sleep for a task. +This document describes the kernel primitives. -### Primitive type +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. -#### Description +Two categories of primitives are documented here: -The lowest primitive type are the Rust types, but beside them there's the kernel primitive type. -Those can be used anywhere in the kernel. Here's a list of the primitive type currently available in the kernel: +- **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. -#### RingBuffer +- **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. -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. +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`. -#### AlignedStack16 +### Primitive type + +#### Description -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`. +There are currently no primitive type implemented in the kernel. ### Task primitive @@ -59,11 +67,12 @@ You can consider the given tick as `1ms`. 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 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. -#### delay +#### Invariants -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`. +- 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. diff --git a/Documentation/kernel/timing_helpers.md b/Documentation/kernel/timing_helpers.md new file mode 100644 index 0000000..2a207fd --- /dev/null +++ b/Documentation/kernel/timing_helpers.md @@ -0,0 +1,22 @@ +# Kernel timing helpers + + +- [Kernel timing helpers](#kernel-timing-helpers) + - [Description](#description) + - [delay](#delay) + - [Invariants](#invariants) + + +## 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. From 50ac62e09ab479bf162e3d79f0a9abf847e33ae6 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 10:48:32 +0100 Subject: [PATCH 20/24] feat: add file info header to primitives task file and update task sleep test behavior Add the info header to primitives task file to keep track of what is tested at this point, update the task sleep test behavior to skipped to not always running it --- src/task/primitives.rs | 19 +++++++++++++++++++ src/tests/task/primitives.rs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/task/primitives.rs b/src/task/primitives.rs index b90458f..e6d4799 100644 --- a/src/task/primitives.rs +++ b/src/task/primitives.rs @@ -1,3 +1,22 @@ +/* +File info: Task primitives. + +Test coverage: yield and sleep. + +Tested: +- yield with two task. +- sleep with invariants from run queue and blocked queue. + +Not tested: +- delay + +Reasons: +- delay is hard to test, for now we test it by just checking it manually. + +Tests files: +- 'src/tests/task/primitives.rs' +*/ + use core::ptr::null_mut; use crate::{ diff --git a/src/tests/task/primitives.rs b/src/tests/task/primitives.rs index 171d5e3..6f07bab 100644 --- a/src/tests/task/primitives.rs +++ b/src/tests/task/primitives.rs @@ -137,7 +137,7 @@ pub fn task_primitives_test_suite() { TestCase::init( "Task primitive sleep", test_task_primitives_sleep, - TestBehavior::Default, + TestBehavior::Skipped, ), ], name: "Task primitives", From 3cb7357a9f863ac1d1c0c2036fa00b8a2cbed75a Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 10:50:08 +0100 Subject: [PATCH 21/24] refactor: remove debug implementation to RingBuffer type --- src/primitives/ring_buff.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/primitives/ring_buff.rs b/src/primitives/ring_buff.rs index aa93e73..b2b54c7 100644 --- a/src/primitives/ring_buff.rs +++ b/src/primitives/ring_buff.rs @@ -23,7 +23,6 @@ References: use crate::{log, logs::LogLevel}; -#[derive(Debug)] pub struct RingBuffer { buff: [Option; N], // Oldest element in the buffer @@ -34,7 +33,7 @@ pub struct RingBuffer { count: usize, } -impl RingBuffer { +impl RingBuffer { pub const fn init() -> Self { RingBuffer { buff: [None; N], From cd23a581012c8f7fde81e9c2c0043f800bbb84e7 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 10:51:26 +0100 Subject: [PATCH 22/24] chore: bump kernel version to 0.4.3 --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/info.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fff8951..b360627 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "lrnrtos" -version = "0.4.2" +version = "0.4.3" dependencies = [ "arrayvec", ] diff --git a/Cargo.toml b/Cargo.toml index f58b703..7b29236 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lrnrtos" -version = "0.4.2" +version = "0.4.3" edition = "2024" [[bin]] diff --git a/src/info.rs b/src/info.rs index d3760f2..30cbdd0 100644 --- a/src/info.rs +++ b/src/info.rs @@ -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"; From 7961edb7879930712d847476c5fa9605ef7c55c4 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 10:59:00 +0100 Subject: [PATCH 23/24] chore: format and remove warning --- src/main.rs | 29 ----------------------------- src/task/primitives.rs | 17 +++++++++++------ 2 files changed, 11 insertions(+), 35 deletions(-) diff --git a/src/main.rs b/src/main.rs index 92a5f43..7486484 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,9 +59,6 @@ use core::panic::PanicInfo; use logs::LogLevel; use mem::mem_kernel_stack_info; use primitives::ring_buff::RingBuffer; -use task::{ - list::task_list_get_task_by_pid, primitives::{delay, sleep, r#yield}, task_context_switch, task_create, CURRENT_TASK_PID, TASK_HANDLER -}; // Static buffer to use as a ready queue for task. pub static mut BUFFER: RingBuffer = RingBuffer::init(); @@ -79,16 +76,6 @@ unsafe extern "C" fn main() -> ! { kernel_stack.bottom ); log!(LogLevel::Info, "LrnRTOS started!"); - task_create("Test sleep", task_fn, 1, 0x200); - task_create("Other test task", test_fn, 1, 0x200); - unsafe { CURRENT_TASK_PID = 1 }; - let mut task = task_list_get_task_by_pid(unsafe { CURRENT_TASK_PID }); - unsafe { TASK_HANDLER = *task.as_mut().unwrap() }; - #[allow(static_mut_refs)] - unsafe { - BUFFER.push(2); - } - task_context_switch(task.unwrap()); loop { log!(LogLevel::Debug, "Main loop uptime."); unsafe { @@ -97,22 +84,6 @@ unsafe extern "C" fn main() -> ! { } } -fn task_fn() -> ! { - loop { - log!(LogLevel::Debug, "Test sleep task function"); - // unsafe { r#yield() }; - unsafe { sleep(10) }; - } -} - -fn test_fn() -> ! { - loop { - log!(LogLevel::Debug, "Always running or ready task"); - unsafe { r#yield() }; - delay(1000); - } -} - #[panic_handler] #[cfg(not(feature = "test"))] fn panic_handler(panic: &PanicInfo) -> ! { diff --git a/src/task/primitives.rs b/src/task/primitives.rs index e6d4799..abee205 100644 --- a/src/task/primitives.rs +++ b/src/task/primitives.rs @@ -17,8 +17,6 @@ Tests files: - 'src/tests/task/primitives.rs' */ -use core::ptr::null_mut; - use crate::{ BLOCKED_QUEUE, BUFFER, arch::traps::interrupt::enable_and_halt, @@ -57,7 +55,7 @@ fn task_set_wake_tick(tick: usize) { /// Block the current task until the given tick is reach. pub fn task_block_until(tick: usize) { let current_task: *mut Task = unsafe { TASK_HANDLER }; - if current_task == null_mut() { + if current_task.is_null() { log!( LogLevel::Error, "Error getting the current task, invariant violated. Sleep couldn't be used outside of a task." @@ -94,6 +92,9 @@ pub fn task_awake_blocked(tick: usize) { log!(LogLevel::Error, "Error getting the oldest pid in run queue"); return; } + // Allow expect, check the value before and if the pid become invalid we don't want to pursue + // run time. + #[allow(clippy::expect_used)] let task = task_list_get_task_by_pid(pid.expect("Error getting the pid behind the Option<>")); if task.is_none() { log!( @@ -114,19 +115,23 @@ pub fn task_awake_blocked(tick: usize) { // push to run queue #[allow(static_mut_refs)] unsafe { + // Allow expect, check the value before and if the pid become invalid we don't want to pursue + // run time. + #[allow(clippy::expect_used)] BUFFER.push(pid.expect("Failed to get the pid behind the Option<>")); }; - return; } else { // push to blocked queue #[allow(static_mut_refs)] unsafe { + // Allow expect, check the value before and if the pid become invalid we don't want to pursue + // run time. + #[allow(clippy::expect_used)] BLOCKED_QUEUE.push(pid.expect("Failed to get the pid behind the Option<>")) }; - return; } } - TaskBlockControl::None => return, + TaskBlockControl::None => (), } } From 231299a44eaab217ef6f146cf0c60d1126fd3905 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 11:09:44 +0100 Subject: [PATCH 24/24] refactor: remove no_mangle use on test_task_primitives_delay --- src/tests/task/primitives.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tests/task/primitives.rs b/src/tests/task/primitives.rs index 6f07bab..33af7a0 100644 --- a/src/tests/task/primitives.rs +++ b/src/tests/task/primitives.rs @@ -95,7 +95,6 @@ fn task_testing_sleep() -> ! { loop {} } -#[unsafe(no_mangle)] fn test_task_primitives_delay() -> u8 { task_create("Test delay", task_fn, 1, 0x1000); unsafe { CURRENT_TASK_PID = 2 };