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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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 }; From c6facc1a951ce9a70d1b402b34fd36f1290d0322 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 5 Feb 2026 10:53:27 +0100 Subject: [PATCH 25/84] feat: update the RUN_QUEUE init, add the idle task, and update main to test Change the BUFFER static to RUN_QUEUE static using a config static for max size, add a basic idle task, static, no change possible for the dev calling it, and update the main function to create task and test. Need sleep primitive to continue on this branch --- src/config.rs | 5 +++++ src/main.rs | 26 +++++++++++++++++++++----- src/scheduler/mod.rs | 28 +++++++++++++++++++++------- src/task/list.rs | 20 ++++++++++++++++++++ src/task/mod.rs | 23 ++++++++++++++++++++++- 5 files changed, 89 insertions(+), 13 deletions(-) diff --git a/src/config.rs b/src/config.rs index 2400fe5..70f2492 100644 --- a/src/config.rs +++ b/src/config.rs @@ -35,6 +35,11 @@ pub static FDT_MAX_PROPS: usize = 128; // ————————————— Define the max size of Task list ————————————— // ———————————————————————————————————————————————————————————— pub static TASK_LIST_MAX_SIZE: usize = 4; +// ———————————————————————————————————————————————————————————— +// ————————————— Define the max size of the Run queue ————————— +// ———————————————————————————————————————————————————————————— +// The run queue is len - 1, if the size is 4, it will only use 3 slot in the queue. +pub static RUN_QUEUE_MAX_SIZE: usize = 3; // Kernel stack size // WARNING diff --git a/src/main.rs b/src/main.rs index 7486484..ea72c99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,16 +54,16 @@ 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; -// 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(); +use task::{ + CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, task_context_switch, + task_create, task_idle_task, +}; #[unsafe(no_mangle)] unsafe extern "C" fn main() -> ! { @@ -76,6 +76,15 @@ unsafe extern "C" fn main() -> ! { kernel_stack.bottom ); log!(LogLevel::Info, "LrnRTOS started!"); + task_idle_task(); + task_create("Test task", test_task, 1, 0x200); + #[allow(static_mut_refs)] + 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() }; + task_context_switch(task.unwrap()); loop { log!(LogLevel::Debug, "Main loop uptime."); unsafe { @@ -84,6 +93,13 @@ unsafe extern "C" fn main() -> ! { } } +fn test_task() -> ! { + loop { + log!(LogLevel::Debug, "Test task, only yield."); + unsafe { r#yield() }; + } +} + #[panic_handler] #[cfg(not(feature = "test"))] fn panic_handler(panic: &PanicInfo) -> ! { diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index 9bd9589..a55841e 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -15,17 +15,23 @@ References: */ use crate::{ - BUFFER, arch::scheduler::{SCHEDULER_CTX, SchedulerCtx, sched_ctx_restore}, - log, + config::RUN_QUEUE_MAX_SIZE, + kprint_fmt, log, logs::LogLevel, + primitives::ring_buff::RingBuffer, task::{ TASK_HANDLER, TaskState, - list::{task_list_get_task_by_pid, task_list_update_task_by_pid}, + list::{task_list_get_idle_task, task_list_get_task_by_pid, task_list_update_task_by_pid}, task_context_switch, task_pid, }, }; +// Store all task Ready +pub static mut RUN_QUEUE: RingBuffer = RingBuffer::init(); +// Queue containing all blocked task. +pub static mut BLOCKED_QUEUE: RingBuffer = RingBuffer::init(); + /// Temporary function use to test the context switch and context restore on multiple task. /// Will certainly be used later on the real scheduler. /// Pop oldest task from RingBuffer, save the task context, update it, and repush it to the @@ -34,6 +40,9 @@ use crate::{ /// Not the best way to use the RingBuffer but it will do. #[unsafe(no_mangle)] pub fn dispatch() { + kprint_fmt!("debug run_queue buff entering dispatch: {:?}\n", unsafe { + RUN_QUEUE.buff + }); // Current running task let mut current_task = unsafe { *TASK_HANDLER }; if current_task.state != TaskState::Blocked { @@ -42,21 +51,26 @@ pub fn dispatch() { task_list_update_task_by_pid(pid, current_task); #[allow(static_mut_refs)] unsafe { - BUFFER.push(pid) + RUN_QUEUE.push(pid) }; } + // Update and load next task #[allow(static_mut_refs)] - let get_next_task = unsafe { BUFFER.pop() }; + let get_next_task = unsafe { RUN_QUEUE.pop() }; if get_next_task.is_none() { log!( - LogLevel::Error, - "Error getting the last task from RingBuffer" + LogLevel::Debug, + "No task available in the run queue, enter idle task." ); + let idle = task_list_get_idle_task(); + #[allow(clippy::expect_used)] + task_context_switch(idle.expect("ERROR: failed to get the idle task, invariant violated.")); } // Allow unwrap because it's a temporary function #[allow(clippy::unwrap_used)] let next_task_pid = get_next_task.unwrap(); + kprint_fmt!("debug pid: {}\n", next_task_pid); // Allow unwrap because it's a temporary function #[allow(clippy::unwrap_used)] let next_task = task_list_get_task_by_pid(next_task_pid).unwrap(); diff --git a/src/task/list.rs b/src/task/list.rs index 9219433..a874910 100644 --- a/src/task/list.rs +++ b/src/task/list.rs @@ -88,6 +88,18 @@ impl TaskList { pub fn get_last_pid(&self) -> u16 { self.last_pid } + + pub fn get_by_priority(&mut self, priority: u8) -> Option<&mut Task> { + for i in 0..TASK_LIST_MAX_SIZE { + let task = unsafe { (*self.list[i].get()).as_mut() }; + if let Some(is_task) = task + && is_task.priority == priority + { + return Some(is_task); + } + } + None + } } pub static mut TASK_LIST: TaskList = TaskList::init(); @@ -131,3 +143,11 @@ pub fn task_list_get_last_pid() -> u16 { TASK_LIST.get_last_pid() } } + +pub fn task_list_get_idle_task<'a>() -> Option<&'a mut Task> { + // Allow static mut refs for now, kernel only run in monocore + #[allow(static_mut_refs)] + unsafe { + TASK_LIST.get_by_priority(0) + } +} diff --git a/src/task/mod.rs b/src/task/mod.rs index 27b1bb5..93340bc 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -19,7 +19,12 @@ Tests files: use list::task_list_add_task; -use crate::{arch::task::task_context::TaskContext, log, logs::LogLevel, mem::mem_task_alloc}; +use crate::{ + arch::{task::task_context::TaskContext, traps::interrupt::enable_and_halt}, + log, + logs::LogLevel, + mem::mem_task_alloc, +}; pub mod list; pub mod primitives; @@ -170,3 +175,19 @@ pub fn task_context_save(task: &Task, ra: usize, sp: usize) { pub fn task_pid(task: &Task) -> u16 { task.pid } + +/// Create the idle task +pub fn task_idle_task() { + let task_name: &str = "Idle task"; + let func: fn() -> ! = idle_task_fn; + let priority: u8 = 0; + let size: usize = 0x100; + task_create(task_name, func, priority, size); +} + +fn idle_task_fn() -> ! { + loop { + log!(LogLevel::Debug, "Idle task."); + unsafe { enable_and_halt() }; + } +} From 78136051b01470b86438ddbcbab248a5e30c3f80 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 5 Feb 2026 10:54:16 +0100 Subject: [PATCH 26/84] feat: update the task_context test to use new RUN_QUEUE and enable the context_switch test --- src/tests/arch/riscv32/task/task_context.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tests/arch/riscv32/task/task_context.rs b/src/tests/arch/riscv32/task/task_context.rs index 33e3ad3..c9f5e46 100644 --- a/src/tests/arch/riscv32/task/task_context.rs +++ b/src/tests/arch/riscv32/task/task_context.rs @@ -1,4 +1,5 @@ -use crate::{BUFFER, print}; +use crate::print; +use crate::scheduler::RUN_QUEUE; use core::{mem, ptr}; use crate::{ @@ -124,7 +125,7 @@ pub fn test_task_context_switch() -> u8 { task_create("B", test_context_switch_b, 0, 0x1000); #[allow(static_mut_refs)] unsafe { - BUFFER.push(3) + RUN_QUEUE.push(3) }; unsafe { CURRENT_TASK_PID = 2 }; let mut task = task_list_get_task_by_pid(unsafe { CURRENT_TASK_PID }); @@ -153,7 +154,7 @@ pub fn task_context_test_suite() { TestCase::init( "Task context switch no invariants violated", test_task_context_switch, - TestBehavior::Skipped, + TestBehavior::Default, ), ], name: "RISC-V32 bit task context layout", From 3a9df137b3989bde190b5afdf6e4d26b503ae8bd Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 11:26:52 +0100 Subject: [PATCH 27/84] refactor: update codebase to use RUN_QUEUE instead of BUFFER --- src/main.rs | 13 ++++++++----- src/scheduler/mod.rs | 3 --- src/task/primitives.rs | 5 ++--- src/tests/task/primitives.rs | 6 +++--- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/main.rs b/src/main.rs index ea72c99..6646969 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,15 +54,16 @@ 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 task::{ - CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, task_context_switch, - task_create, task_idle_task, + CURRENT_TASK_PID, TASK_HANDLER, + list::task_list_get_task_by_pid, + primitives::{sleep, r#yield}, + task_context_switch, task_create, task_idle_task, }; #[unsafe(no_mangle)] @@ -95,8 +96,10 @@ unsafe extern "C" fn main() -> ! { fn test_task() -> ! { loop { - log!(LogLevel::Debug, "Test task, only yield."); - unsafe { r#yield() }; + log!(LogLevel::Debug, "Test task, only sleep."); + unsafe { + sleep(20); + } } } diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index a55841e..136d616 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -40,9 +40,6 @@ pub static mut BLOCKED_QUEUE: RingBuffer = RingBuffer::init(); /// Not the best way to use the RingBuffer but it will do. #[unsafe(no_mangle)] pub fn dispatch() { - kprint_fmt!("debug run_queue buff entering dispatch: {:?}\n", unsafe { - RUN_QUEUE.buff - }); // Current running task let mut current_task = unsafe { *TASK_HANDLER }; if current_task.state != TaskState::Blocked { diff --git a/src/task/primitives.rs b/src/task/primitives.rs index abee205..0f3b336 100644 --- a/src/task/primitives.rs +++ b/src/task/primitives.rs @@ -18,12 +18,11 @@ Tests files: */ use crate::{ - BLOCKED_QUEUE, BUFFER, arch::traps::interrupt::enable_and_halt, ktime::{set_ktime_ms, tick::get_tick}, log, logs::LogLevel, - scheduler::dispatch, + scheduler::{BLOCKED_QUEUE, RUN_QUEUE, dispatch}, }; use super::{ @@ -118,7 +117,7 @@ pub fn task_awake_blocked(tick: usize) { // 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<>")); + RUN_QUEUE.push(pid.expect("Failed to get the pid behind the Option<>")); }; } else { // push to blocked queue diff --git a/src/tests/task/primitives.rs b/src/tests/task/primitives.rs index 33af7a0..aca0f93 100644 --- a/src/tests/task/primitives.rs +++ b/src/tests/task/primitives.rs @@ -1,5 +1,4 @@ use crate::{ - BLOCKED_QUEUE, BUFFER, arch::traps::{ disable_interrupts, enable_interrupts, handler::trap_handler, @@ -9,6 +8,7 @@ use crate::{ config::TICK_SAFETY_DURATION, kprint, ktime::{set_ktime_ms, set_ktime_seconds, tick::get_tick}, + scheduller::{BLOCKED_QUEUE, RUN_QUEUE}, task::{ CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, @@ -53,7 +53,7 @@ fn task_testing_sleep() -> ! { #[allow(static_mut_refs)] let blocked_queue = unsafe { &BLOCKED_QUEUE }; #[allow(static_mut_refs)] - let run_queue = unsafe { &BUFFER }; + let run_queue = unsafe { &RUN_QUEUE }; 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 @@ -119,7 +119,7 @@ fn test_task_primitives_sleep() -> u8 { unsafe { TASK_HANDLER = *task.as_mut().unwrap() }; #[allow(static_mut_refs)] unsafe { - BUFFER.push(3); + RUN_QUEUE.push(3); } task_context_switch(task.unwrap()); 0 From dad1a47c9d80909dc09dd6fe14c9a3eebd985794 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 11:52:27 +0100 Subject: [PATCH 28/84] chore: remove unused import --- src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6646969..5dbfabd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,9 +60,7 @@ use logs::LogLevel; use mem::mem_kernel_stack_info; use task::{ - CURRENT_TASK_PID, TASK_HANDLER, - list::task_list_get_task_by_pid, - primitives::{sleep, r#yield}, + CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, primitives::sleep, task_context_switch, task_create, task_idle_task, }; From d22794bff649fc4a8dc17607bd03a36c1385b074 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 15:15:45 +0100 Subject: [PATCH 29/84] feat: add a new CpusState structure and public API for it The CpusState structure will be used to store all useful flags per CPU core. Like scheduler flags, state flags, and more if needed. --- src/arch/riscv32/helpers.rs | 7 ++++ src/arch/riscv32/mod.rs | 1 + src/config.rs | 4 +++ src/misc.rs | 69 +++++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 src/arch/riscv32/helpers.rs diff --git a/src/arch/riscv32/helpers.rs b/src/arch/riscv32/helpers.rs new file mode 100644 index 0000000..8e3da4f --- /dev/null +++ b/src/arch/riscv32/helpers.rs @@ -0,0 +1,7 @@ +use crate::arch::asm; + +pub fn current_cpu_core() -> usize { + let id: usize = 0; + unsafe { asm!("csrr {}, mhartid", out(reg) id) }; + id +} diff --git a/src/arch/riscv32/mod.rs b/src/arch/riscv32/mod.rs index 65da303..405b2b1 100644 --- a/src/arch/riscv32/mod.rs +++ b/src/arch/riscv32/mod.rs @@ -4,3 +4,4 @@ pub mod scheduler; pub mod start; pub mod task; pub mod traps; +pub mod helpers; diff --git a/src/config.rs b/src/config.rs index 70f2492..bb2197c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -40,6 +40,10 @@ pub static TASK_LIST_MAX_SIZE: usize = 4; // ———————————————————————————————————————————————————————————— // The run queue is len - 1, if the size is 4, it will only use 3 slot in the queue. pub static RUN_QUEUE_MAX_SIZE: usize = 3; +// ———————————————————————————————————————————————————————————— +// ————————————— Define the number of CPU core ———————————————— +// ———————————————————————————————————————————————————————————— +pub static CPU_CORE_NUMBER: usize = 1; // Kernel stack size // WARNING diff --git a/src/misc.rs b/src/misc.rs index 3abefa1..badb99c 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -1,5 +1,74 @@ +use crate::{arch, config::CPU_CORE_NUMBER}; + #[repr(C)] pub struct RawTraitObject { pub data: *const (), pub vtable: *const (), } + +pub static mut CPUS_STATE: CpusState = CpusState::init(); + +#[repr(C)] +struct CpusState { + // Flags for the CPU states, not used yet. + cpu_state: [u8; CPU_CORE_NUMBER], + // Flags for the CPU scheduler state. + // bit 0: scheduler state, init or not. + // bit 1: need reschedule or not. + // bit 2:7: reschedule reason. + scheduler_state: [u8; CPU_CORE_NUMBER], +} + +impl CpusState { + const fn init() -> Self { + CpusState { + cpu_state: [0u8; CPU_CORE_NUMBER], + scheduler_state: [0u8; CPU_CORE_NUMBER], + } + } + + fn scheduler_set_reschedule_bit(&mut self, core: usize) { + let mut state = self.scheduler_state[core]; + let mask = 1 << 1; + // Set need reschedule bit. + state = state | mask; + } + + fn scheduler_clear_reschedule_bit(&mut self, core: usize) { + let mut state = self.scheduler_state[core]; + let mask = 0 << 1; + // Clear need reschedule bit. + state = state | mask; + } + + fn scheduler_read_reschedule_bit(&self, core: usize) -> bool { + let mut state = self.scheduler_state[core]; + let mask = 1 << 1; + // Get the bit 1 + state = state & mask; + } +} + +pub fn need_reschedule() { + let current_core = arch::helpers::current_cpu_core(); + #[allow(static_mut_refs)] + unsafe { + CPUS_STATE.scheduler_set_reschedule_bit(current_core) + }; +} + +pub fn clear_reschedule() { + let current_core = arch::helpers::current_cpu_core(); + #[allow(static_mut_refs)] + unsafe { + CPUS_STATE.scheduler_clear_reschedule_bit(current_core) + }; +} + +pub fn read_need_reschedule() { + let current_core = arch::helpers::current_cpu_core(); + #[allow(static_mut_refs)] + unsafe { + CPUS_STATE.scheduler_read_reschedule_bit(current_core) + }; +} From 9231cf5379a276b97b7ef54b9e937fafaa14b121 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 15:55:47 +0100 Subject: [PATCH 30/84] feat: update trap entry to handle reschedule, update method and public API for CPUS_STATE The reschedule work only once, then it won't work after that, weird but there's still improvement --- src/arch/riscv32/asm/trap_entry.S | 7 +++++++ src/arch/riscv32/helpers.rs | 4 ++-- src/misc.rs | 25 ++++++++++++++++++++++--- src/scheduler/mod.rs | 8 ++++++-- src/task/primitives.rs | 2 ++ 5 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/arch/riscv32/asm/trap_entry.S b/src/arch/riscv32/asm/trap_entry.S index bfddcb9..42d9249 100644 --- a/src/arch/riscv32/asm/trap_entry.S +++ b/src/arch/riscv32/asm/trap_entry.S @@ -50,6 +50,13 @@ trap_entry: load_gp %i .set i, i+1 .endr + # Check if a re-schedule is needed or not. + call read_need_reschedule + # If a0 != 0, goto 1f, else mret + bnez a0, 1f + mret +1: + call dispatch mret # Function used when the trap strack = 0, just infinite loop for debuging purpose diff --git a/src/arch/riscv32/helpers.rs b/src/arch/riscv32/helpers.rs index 8e3da4f..34db408 100644 --- a/src/arch/riscv32/helpers.rs +++ b/src/arch/riscv32/helpers.rs @@ -1,7 +1,7 @@ -use crate::arch::asm; +use core::arch::asm; pub fn current_cpu_core() -> usize { - let id: usize = 0; + let mut id: usize = 0; unsafe { asm!("csrr {}, mhartid", out(reg) id) }; id } diff --git a/src/misc.rs b/src/misc.rs index badb99c..8ad029b 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -27,11 +27,16 @@ impl CpusState { } } + fn read_scheduler_flag<'a>(&'a self, core: usize) -> &'a u8 { + &self.scheduler_state[core] + } + fn scheduler_set_reschedule_bit(&mut self, core: usize) { let mut state = self.scheduler_state[core]; let mask = 1 << 1; // Set need reschedule bit. state = state | mask; + self.scheduler_state[core] = state; } fn scheduler_clear_reschedule_bit(&mut self, core: usize) { @@ -39,13 +44,18 @@ impl CpusState { let mask = 0 << 1; // Clear need reschedule bit. state = state | mask; + self.scheduler_state[core] = state; } fn scheduler_read_reschedule_bit(&self, core: usize) -> bool { let mut state = self.scheduler_state[core]; - let mask = 1 << 1; // Get the bit 1 - state = state & mask; + let flag = (state >> 1) & 1; + if flag == 1 { + return true; + } else { + return false; + } } } @@ -65,10 +75,19 @@ pub fn clear_reschedule() { }; } -pub fn read_need_reschedule() { +#[unsafe(no_mangle)] +pub fn read_need_reschedule() -> bool { let current_core = arch::helpers::current_cpu_core(); #[allow(static_mut_refs)] unsafe { CPUS_STATE.scheduler_read_reschedule_bit(current_core) + } +} + +pub fn read_scheduler_flag<'a>() -> &'a u8 { + let current_core = arch::helpers::current_cpu_core(); + #[allow(static_mut_refs)] + unsafe { + return CPUS_STATE.read_scheduler_flag(current_core); }; } diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index 136d616..e53b675 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -19,6 +19,7 @@ use crate::{ config::RUN_QUEUE_MAX_SIZE, kprint_fmt, log, logs::LogLevel, + misc::{clear_reschedule, read_need_reschedule}, primitives::ring_buff::RingBuffer, task::{ TASK_HANDLER, TaskState, @@ -51,7 +52,11 @@ pub fn dispatch() { RUN_QUEUE.push(pid) }; } - + let resched = read_need_reschedule(); + if resched { + kprint_fmt!("debug\n"); + clear_reschedule(); + } // Update and load next task #[allow(static_mut_refs)] let get_next_task = unsafe { RUN_QUEUE.pop() }; @@ -67,7 +72,6 @@ pub fn dispatch() { // Allow unwrap because it's a temporary function #[allow(clippy::unwrap_used)] let next_task_pid = get_next_task.unwrap(); - kprint_fmt!("debug pid: {}\n", next_task_pid); // Allow unwrap because it's a temporary function #[allow(clippy::unwrap_used)] let next_task = task_list_get_task_by_pid(next_task_pid).unwrap(); diff --git a/src/task/primitives.rs b/src/task/primitives.rs index 0f3b336..fdbb991 100644 --- a/src/task/primitives.rs +++ b/src/task/primitives.rs @@ -22,6 +22,7 @@ use crate::{ ktime::{set_ktime_ms, tick::get_tick}, log, logs::LogLevel, + misc::need_reschedule, scheduler::{BLOCKED_QUEUE, RUN_QUEUE, dispatch}, }; @@ -118,6 +119,7 @@ pub fn task_awake_blocked(tick: usize) { // run time. #[allow(clippy::expect_used)] RUN_QUEUE.push(pid.expect("Failed to get the pid behind the Option<>")); + need_reschedule(); }; } else { // push to blocked queue From 027e5cdf58949f3d26ed7a8fa9fa73f5d23e834d Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 16:04:19 +0100 Subject: [PATCH 31/84] feat: update the dispatch clear rechedule bit log to be a constant debug log, fix the clear need resched bit to use correct bitwise --- src/misc.rs | 2 +- src/scheduler/mod.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/misc.rs b/src/misc.rs index 8ad029b..9d263cd 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -43,7 +43,7 @@ impl CpusState { let mut state = self.scheduler_state[core]; let mask = 0 << 1; // Clear need reschedule bit. - state = state | mask; + state = state & mask; self.scheduler_state[core] = state; } diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index e53b675..b34a43a 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -54,7 +54,10 @@ pub fn dispatch() { } let resched = read_need_reschedule(); if resched { - kprint_fmt!("debug\n"); + log!( + LogLevel::Debug, + "Reschedule needed, clearing the need reschedule bit." + ); clear_reschedule(); } // Update and load next task From 935b8807eeb2afda8af65df36b1507305700bd28 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 09:21:42 +0100 Subject: [PATCH 32/84] feat: update the task context to save and restore mstatus Trying to fix the bug on the idle task, after some time running task and idle task it crash. Don't know why --- src/arch/riscv32/asm/context_offset.S | 5 +++-- src/arch/riscv32/asm/restore_context.S | 3 +++ src/arch/riscv32/asm/save_context.S | 3 +++ src/arch/riscv32/task/task_context.rs | 6 ++++-- src/arch/riscv32/traps/interrupt.rs | 6 ++++++ 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/arch/riscv32/asm/context_offset.S b/src/arch/riscv32/asm/context_offset.S index 07c4a6b..fab291f 100644 --- a/src/arch/riscv32/asm/context_offset.S +++ b/src/arch/riscv32/asm/context_offset.S @@ -11,6 +11,7 @@ .set OFFSET_SP, 140 # Return address .set OFFSET_RA, 144 +.set OFFSET_MSTATUS, 148 # Optionnal flags, will be use later maybe -.set OFFSET_FLAGS, 148 -.set OFFSET_INSTRUCTION_REG, 151 +.set OFFSET_FLAGS, 152 +.set OFFSET_INSTRUCTION_REG, 155 diff --git a/src/arch/riscv32/asm/restore_context.S b/src/arch/riscv32/asm/restore_context.S index 4ae7603..61f7965 100644 --- a/src/arch/riscv32/asm/restore_context.S +++ b/src/arch/riscv32/asm/restore_context.S @@ -12,6 +12,9 @@ restore_context: # Restore sp from current task context structure lw t0, OFFSET_SP(t6) mv sp, t0 + ## Update mstatus + lw t0, OFFSET_MSTATUS(t6) + csrw mstatus, t0 # Restore current task context using GNU macro # GNU macros from `src/arch/riscv32/asm/gnu_macro.S` .set i, 1 diff --git a/src/arch/riscv32/asm/save_context.S b/src/arch/riscv32/asm/save_context.S index 97f0b54..de3d8e5 100644 --- a/src/arch/riscv32/asm/save_context.S +++ b/src/arch/riscv32/asm/save_context.S @@ -9,6 +9,9 @@ save_context: sw a1, OFFSET_RA(t6) # Store word from t0(sp) in context structure sw a2, OFFSET_SP(t6) + # Save mstatus + csrr t0, mstatus + sw t0, OFFSET_MSTATUS(t6) # Save current task context using GNU macro # GNU macros from `src/arch/riscv32/asm/gnu_macro.S` .set i, 1 diff --git a/src/arch/riscv32/task/task_context.rs b/src/arch/riscv32/task/task_context.rs index 09d4165..ff183ee 100644 --- a/src/arch/riscv32/task/task_context.rs +++ b/src/arch/riscv32/task/task_context.rs @@ -25,8 +25,9 @@ pub struct TaskContext { pub pc: u32, // Offset 136 pub sp: u32, // Offset 140 pub ra: u32, // Offset 144 - pub flags: [u8; 3], // Offset 148 (first index 144; second index 145, third index 146) - pub instruction_register: u8, // Offset 151 + pub mstatus: u32, // Offset 148 + pub flags: [u8; 3], // Offset 152 (first index 152; second index 153, third index 154) + pub instruction_register: u8, // Offset 155 } impl TaskContext { @@ -37,6 +38,7 @@ impl TaskContext { pc: func as usize as u32, sp: size[0] as u32, ra: func as usize as u32, + mstatus: 136, flags: [0u8; 3], instruction_register: 0, } diff --git a/src/arch/riscv32/traps/interrupt.rs b/src/arch/riscv32/traps/interrupt.rs index bac5507..3543b39 100644 --- a/src/arch/riscv32/traps/interrupt.rs +++ b/src/arch/riscv32/traps/interrupt.rs @@ -98,6 +98,12 @@ pub fn disable_mstatus_mie() { unsafe { asm!("csrrc zero, mstatus, {}", in(reg) MIE) }; } +pub fn read_mstatus() -> u32 { + let value: u32; + unsafe { asm!("csrr {}, mstatus", out(reg) value) }; + value +} + // Machine Trap-Vector CSR pub fn mtvec_switch_to_vectored_mode() { From 6964d435c8d5294613534fbd1d20e04a8aacef8d Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 09:33:23 +0100 Subject: [PATCH 33/84] feat: update default mstatus in task context to 8 --- src/arch/riscv32/task/task_context.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/arch/riscv32/task/task_context.rs b/src/arch/riscv32/task/task_context.rs index ff183ee..fa7f034 100644 --- a/src/arch/riscv32/task/task_context.rs +++ b/src/arch/riscv32/task/task_context.rs @@ -38,7 +38,8 @@ impl TaskContext { pc: func as usize as u32, sp: size[0] as u32, ra: func as usize as u32, - mstatus: 136, + // Set mstatus to 8 by default to enable mie + mstatus: 8, flags: [0u8; 3], instruction_register: 0, } From 2c958f3da3c18ffb95b99c594ba168c0fa269a1c Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 09:40:21 +0100 Subject: [PATCH 34/84] chore: remove unused import and format files --- src/misc.rs | 4 ++-- src/scheduler/mod.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/misc.rs b/src/misc.rs index 9d263cd..6540bed 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -6,7 +6,7 @@ pub struct RawTraitObject { pub vtable: *const (), } -pub static mut CPUS_STATE: CpusState = CpusState::init(); +static mut CPUS_STATE: CpusState = CpusState::init(); #[repr(C)] struct CpusState { @@ -48,7 +48,7 @@ impl CpusState { } fn scheduler_read_reschedule_bit(&self, core: usize) -> bool { - let mut state = self.scheduler_state[core]; + let state = self.scheduler_state[core]; // Get the bit 1 let flag = (state >> 1) & 1; if flag == 1 { diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index b34a43a..0f7ab73 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -15,10 +15,10 @@ References: */ use crate::{ + LogLevel, arch::scheduler::{SCHEDULER_CTX, SchedulerCtx, sched_ctx_restore}, config::RUN_QUEUE_MAX_SIZE, - kprint_fmt, log, - logs::LogLevel, + log, misc::{clear_reschedule, read_need_reschedule}, primitives::ring_buff::RingBuffer, task::{ From 1d198d94b23670b1d0441f8aa8787e7ca2bb51b4 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 09:57:02 +0100 Subject: [PATCH 35/84] feat: update task context test to use updated offset and fix wrong import on primitives task test The test for context switch doesn't work, updating task context with mstatus broke the test. The context switch still work; just tested it in main. But the test mode need refactor to use correctly trap handling --- src/tests/arch/riscv32/task/task_context.rs | 16 +++++++++++++--- src/tests/task/primitives.rs | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/tests/arch/riscv32/task/task_context.rs b/src/tests/arch/riscv32/task/task_context.rs index c9f5e46..597fedc 100644 --- a/src/tests/arch/riscv32/task/task_context.rs +++ b/src/tests/arch/riscv32/task/task_context.rs @@ -51,6 +51,12 @@ pub fn test_task_context_init() -> u8 { "Task context has been initialized with wrong SP, expect sp to be set to the hi address of the task address space" ); } + // Check mstatus + if task_context.mstatus != 8 { + panic!( + "Task context has been initialized with wrong mstatus, expect mstatus to be set to 8 to only enable mstatus.mie" + ); + } 0 } @@ -73,14 +79,18 @@ pub fn test_task_context_offset() -> u8 { } let ra_off = mem::offset_of!(TaskContext, ra); if ra_off != 144 { - panic!("Task context ra offset must be 144, got: {sp_off}"); + panic!("Task context ra offset must be 144, got: {ra_off}"); } + let mstatus_off = mem::offset_of!(TaskContext, mstatus); + if mstatus_off != 148 { + panic!("Task context mstatus offset must be 148, got: {mstatus_off}"); + } let flags_off = mem::offset_of!(TaskContext, flags); - if flags_off != 148 { + if flags_off != 152 { panic!("Task context flags offset must be 144, got: {flags_off}"); } let instruction_reg_off = mem::offset_of!(TaskContext, instruction_register); - if instruction_reg_off != 151 { + if instruction_reg_off != 155 { panic!("Task context instruction_register offset must be 147, got: {instruction_reg_off}"); }; 0 diff --git a/src/tests/task/primitives.rs b/src/tests/task/primitives.rs index aca0f93..c96605c 100644 --- a/src/tests/task/primitives.rs +++ b/src/tests/task/primitives.rs @@ -8,7 +8,7 @@ use crate::{ config::TICK_SAFETY_DURATION, kprint, ktime::{set_ktime_ms, set_ktime_seconds, tick::get_tick}, - scheduller::{BLOCKED_QUEUE, RUN_QUEUE}, + scheduler::{BLOCKED_QUEUE, RUN_QUEUE}, task::{ CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, From 3d5832741b24db63a9b51ae2b698f6e079cf4b6b Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 10:12:17 +0100 Subject: [PATCH 36/84] feat: remove the test for the idle task in main, put the cfg with idle_task feature on the idle task creation Added a idle_task feature for the idle task creation. Avoid to always have it enable. --- src/main.rs | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5dbfabd..ef99bda 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,10 +59,8 @@ use core::panic::PanicInfo; use logs::LogLevel; use mem::mem_kernel_stack_info; -use task::{ - CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, primitives::sleep, - task_context_switch, task_create, task_idle_task, -}; +#[cfg(feature = "idle_task")] +use task::task_idle_task; #[unsafe(no_mangle)] unsafe extern "C" fn main() -> ! { @@ -75,32 +73,16 @@ unsafe extern "C" fn main() -> ! { kernel_stack.bottom ); log!(LogLevel::Info, "LrnRTOS started!"); + #[cfg(feature = "idle_task")] task_idle_task(); - task_create("Test task", test_task, 1, 0x200); - #[allow(static_mut_refs)] - 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() }; - task_context_switch(task.unwrap()); loop { - log!(LogLevel::Debug, "Main loop uptime."); + log!(LogLevel::Debug, "Main loop."); unsafe { arch::traps::interrupt::enable_and_halt(); } } } -fn test_task() -> ! { - loop { - log!(LogLevel::Debug, "Test task, only sleep."); - unsafe { - sleep(20); - } - } -} - #[panic_handler] #[cfg(not(feature = "test"))] fn panic_handler(panic: &PanicInfo) -> ! { From a27dcc757a9e317a9ede9ed1ecf61f26fbc60860 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 10:13:02 +0100 Subject: [PATCH 37/84] chore: bump kernel version to 0.4.4 Also update the Cargo.toml with comment on feature and add the idle_task feature --- Cargo.lock | 2 +- Cargo.toml | 7 ++++++- src/info.rs | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b360627..560ea25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "lrnrtos" -version = "0.4.3" +version = "0.4.4" dependencies = [ "arrayvec", ] diff --git a/Cargo.toml b/Cargo.toml index 7b29236..a1efcbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lrnrtos" -version = "0.4.3" +version = "0.4.4" edition = "2024" [[bin]] @@ -12,6 +12,11 @@ arrayvec = { version = "0.7.6", default-features = false } [features] default = ["logs", "kprint"] +# Enable kernel logs logs = [] +# Enable early print using statically define uart device kprint = [] +# Switch to test mode test = [] +# Enable the idle task. +idle_task = [] diff --git a/src/info.rs b/src/info.rs index 30cbdd0..adcd61f 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.3"; +pub static KERNEL_VERSION: &str = "0.4.4"; From 56b429930f7443f9e08ed9d3d330530379b3a064 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 10:22:04 +0100 Subject: [PATCH 38/84] docs(kernel): update the task documentation and add idle task section Update the definition of task with the structure, and add a small definition for the idle task --- Documentation/kernel/task.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Documentation/kernel/task.md b/Documentation/kernel/task.md index 6ce2749..71e4913 100644 --- a/Documentation/kernel/task.md +++ b/Documentation/kernel/task.md @@ -6,6 +6,7 @@ - [Purpose](#purpose) - [Structure](#structure) - [How task is store](#how-task-is-store) + - [Idle task](#idle-task) - [Invariants](#invariants) - [References](#references) @@ -31,11 +32,20 @@ enum TaskState { Terminated, } +pub enum TaskBlockControl { + // Store the awake tick for task awakening. + AwakeTick(usize), + // No reason for the task block + None, +} + #[repr(C)] struct Task { // Arch dependant context, don't handle this field in task, only use struct method when // interacting with it. context: TaskContext, + // Task block control, define the reason the task is blocked. + block_control: TaskBlockControl, // Fn ptr to task entry point, this must never return. // This will surely disappear func: fn() -> !, @@ -53,6 +63,8 @@ pub struct TaskContext { pub address_space: [u32; 2], pub pc: u32, pub sp: u32, + pub ra: u32, + pub mstatus: u32, pub flags: [u8; 3], pub instruction_register: u8, } @@ -76,6 +88,12 @@ pub struct TaskList { } ``` +## Idle task + +The idle task is used to ensure that the kernel as always at least one task able to run. +This task is created at the lowest priority to ensure it does not use any CPU time if there are higher priority application tasks in the run queue. +It is not possible to update the idle task, it's a static defined task. + ## Invariants - The task's function must never return. From ab62c06916153a3bc9e49cb8c6ab7531e1e17039 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 10:36:09 +0100 Subject: [PATCH 39/84] chore: format files and remove warnings --- src/arch/riscv32/mod.rs | 2 +- src/misc.rs | 16 ++++++---------- src/tests/arch/riscv32/task/task_context.rs | 2 +- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/arch/riscv32/mod.rs b/src/arch/riscv32/mod.rs index 405b2b1..f724b04 100644 --- a/src/arch/riscv32/mod.rs +++ b/src/arch/riscv32/mod.rs @@ -1,7 +1,7 @@ pub mod asm; +pub mod helpers; pub mod mem; pub mod scheduler; pub mod start; pub mod task; pub mod traps; -pub mod helpers; diff --git a/src/misc.rs b/src/misc.rs index 6540bed..45e750d 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -27,7 +27,7 @@ impl CpusState { } } - fn read_scheduler_flag<'a>(&'a self, core: usize) -> &'a u8 { + fn read_scheduler_flag(&self, core: usize) -> &u8 { &self.scheduler_state[core] } @@ -35,7 +35,7 @@ impl CpusState { let mut state = self.scheduler_state[core]; let mask = 1 << 1; // Set need reschedule bit. - state = state | mask; + state |= mask; self.scheduler_state[core] = state; } @@ -43,7 +43,7 @@ impl CpusState { let mut state = self.scheduler_state[core]; let mask = 0 << 1; // Clear need reschedule bit. - state = state & mask; + state &= mask; self.scheduler_state[core] = state; } @@ -51,11 +51,7 @@ impl CpusState { let state = self.scheduler_state[core]; // Get the bit 1 let flag = (state >> 1) & 1; - if flag == 1 { - return true; - } else { - return false; - } + flag == 1 } } @@ -88,6 +84,6 @@ pub fn read_scheduler_flag<'a>() -> &'a u8 { let current_core = arch::helpers::current_cpu_core(); #[allow(static_mut_refs)] unsafe { - return CPUS_STATE.read_scheduler_flag(current_core); - }; + CPUS_STATE.read_scheduler_flag(current_core) + } } diff --git a/src/tests/arch/riscv32/task/task_context.rs b/src/tests/arch/riscv32/task/task_context.rs index 597fedc..4149d43 100644 --- a/src/tests/arch/riscv32/task/task_context.rs +++ b/src/tests/arch/riscv32/task/task_context.rs @@ -84,7 +84,7 @@ pub fn test_task_context_offset() -> u8 { let mstatus_off = mem::offset_of!(TaskContext, mstatus); if mstatus_off != 148 { panic!("Task context mstatus offset must be 148, got: {mstatus_off}"); - } + } let flags_off = mem::offset_of!(TaskContext, flags); if flags_off != 152 { panic!("Task context flags offset must be 144, got: {flags_off}"); From 309492bf820b5e2c39965bec13dbd1b8be79d437 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 10:42:41 +0100 Subject: [PATCH 40/84] feat: skipped the task context test and add the info header on misc file --- src/misc.rs | 16 ++++++++++++++++ src/tests/arch/riscv32/task/task_context.rs | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/misc.rs b/src/misc.rs index 45e750d..54461d9 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -1,3 +1,19 @@ +/* +File info: Miscellaneous + +Test coverage: ... + +Tested: + +Not tested: + +Reasons: +- Will be refactor and tested in the scheduler update task. + +Tests files: +- ... +*/ + use crate::{arch, config::CPU_CORE_NUMBER}; #[repr(C)] diff --git a/src/tests/arch/riscv32/task/task_context.rs b/src/tests/arch/riscv32/task/task_context.rs index 4149d43..e482183 100644 --- a/src/tests/arch/riscv32/task/task_context.rs +++ b/src/tests/arch/riscv32/task/task_context.rs @@ -164,7 +164,7 @@ pub fn task_context_test_suite() { TestCase::init( "Task context switch no invariants violated", test_task_context_switch, - TestBehavior::Default, + TestBehavior::Skipped, ), ], name: "RISC-V32 bit task context layout", From a7ec0ee2cabd22c8b626a9a99d58631692cae8dc Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 11:53:14 +0100 Subject: [PATCH 41/84] feat: add the run queue bitmap and update the run queue data structure for preemptive scheduling --- src/config.rs | 5 ++++- src/scheduler/mod.rs | 13 ++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/config.rs b/src/config.rs index bb2197c..7232ba1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,7 +17,10 @@ pub static LOG_LEVEL: LogLevel = LogLevel::Debug; // Define the uart address to use in kprint pub static KPRINT_ADDRESS: usize = 0x1000_0000; - +// ———————————————————————————————————————————————————————————— +// ——————— Define the max priority available for a task ——————— +// ———————————————————————————————————————————————————————————— +pub static TASK_MAX_PRIORITY: u8 = 32; // ———————————————————————————————————————————————————————————— // ———————— Define the max size of devices sub-systems ———————— // ———————————————————————————————————————————————————————————— diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index 0f7ab73..995c4af 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -17,7 +17,7 @@ References: use crate::{ LogLevel, arch::scheduler::{SCHEDULER_CTX, SchedulerCtx, sched_ctx_restore}, - config::RUN_QUEUE_MAX_SIZE, + config::{CPU_CORE_NUMBER, RUN_QUEUE_MAX_SIZE, TASK_MAX_PRIORITY}, log, misc::{clear_reschedule, read_need_reschedule}, primitives::ring_buff::RingBuffer, @@ -28,8 +28,15 @@ use crate::{ }, }; -// Store all task Ready -pub static mut RUN_QUEUE: RingBuffer = RingBuffer::init(); +// Reflect the run queue state +// Array of bitmaps, one bitmap per CPU core +pub static mut RUN_QUEUE_BITMAP: [u32; CPU_CORE_NUMBER] = [0u32; CPU_CORE_NUMBER]; +// Array of run queue per priority +// Each index of this array is a priority, and at each index, there's a ring buffer of all task +// with that priority. +// We use the RUN_QUEUE_BITMAP to easily find the buffer with the highest priority to look into. +pub static mut RUN_QUEUE: [RingBuffer; TASK_MAX_PRIORITY] = + [RingBuffer::init(); TASK_MAX_PRIORITY]; // Queue containing all blocked task. pub static mut BLOCKED_QUEUE: RingBuffer = RingBuffer::init(); From 41b97ce9b01104ffb64f301891f8e6430d3d5006 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 14:32:34 +0100 Subject: [PATCH 42/84] feat: improve run and block queue type to handle multi core arch for future use Also update the type of task max priority in config file --- src/config.rs | 5 +++-- src/scheduler/mod.rs | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/config.rs b/src/config.rs index 7232ba1..454be6e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -20,7 +20,7 @@ pub static KPRINT_ADDRESS: usize = 0x1000_0000; // ———————————————————————————————————————————————————————————— // ——————— Define the max priority available for a task ——————— // ———————————————————————————————————————————————————————————— -pub static TASK_MAX_PRIORITY: u8 = 32; +pub static TASK_MAX_PRIORITY: usize = 32; // ———————————————————————————————————————————————————————————— // ———————— Define the max size of devices sub-systems ———————— // ———————————————————————————————————————————————————————————— @@ -39,10 +39,11 @@ pub static FDT_MAX_PROPS: usize = 128; // ———————————————————————————————————————————————————————————— pub static TASK_LIST_MAX_SIZE: usize = 4; // ———————————————————————————————————————————————————————————— -// ————————————— Define the max size of the Run queue ————————— +// ———— Define the max size of the task run/blocked queue ————— // ———————————————————————————————————————————————————————————— // The run queue is len - 1, if the size is 4, it will only use 3 slot in the queue. pub static RUN_QUEUE_MAX_SIZE: usize = 3; +pub static BLOCK_QUEUE_MAX_SIZE: usize = 3; // ———————————————————————————————————————————————————————————— // ————————————— Define the number of CPU core ———————————————— // ———————————————————————————————————————————————————————————— diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index 995c4af..e91e553 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -17,7 +17,7 @@ References: use crate::{ LogLevel, arch::scheduler::{SCHEDULER_CTX, SchedulerCtx, sched_ctx_restore}, - config::{CPU_CORE_NUMBER, RUN_QUEUE_MAX_SIZE, TASK_MAX_PRIORITY}, + config::{BLOCK_QUEUE_MAX_SIZE, CPU_CORE_NUMBER, RUN_QUEUE_MAX_SIZE, TASK_MAX_PRIORITY}, log, misc::{clear_reschedule, read_need_reschedule}, primitives::ring_buff::RingBuffer, @@ -35,10 +35,11 @@ pub static mut RUN_QUEUE_BITMAP: [u32; CPU_CORE_NUMBER] = [0u32; CPU_CORE_NUMBER // Each index of this array is a priority, and at each index, there's a ring buffer of all task // with that priority. // We use the RUN_QUEUE_BITMAP to easily find the buffer with the highest priority to look into. -pub static mut RUN_QUEUE: [RingBuffer; TASK_MAX_PRIORITY] = - [RingBuffer::init(); TASK_MAX_PRIORITY]; +pub static mut RUN_QUEUE: [[RingBuffer; TASK_MAX_PRIORITY]; + CPU_CORE_NUMBER] = [[const { RingBuffer::init() }; TASK_MAX_PRIORITY]; CPU_CORE_NUMBER]; // Queue containing all blocked task. -pub static mut BLOCKED_QUEUE: RingBuffer = RingBuffer::init(); +pub static mut BLOCKED_QUEUE: [[RingBuffer; TASK_MAX_PRIORITY]; + CPU_CORE_NUMBER] = [[const { RingBuffer::init() }; TASK_MAX_PRIORITY]; CPU_CORE_NUMBER]; /// Temporary function use to test the context switch and context restore on multiple task. /// Will certainly be used later on the real scheduler. From 3571e5ee471c196419d4b5ee28d9b0b28bf42a26 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 15:28:15 +0100 Subject: [PATCH 43/84] feat(primitives): add basic bitmap datastructure --- src/primitives/bitmap.rs | 39 +++++++++++++++++++++++++++++++++++++++ src/primitives/mod.rs | 1 + 2 files changed, 40 insertions(+) create mode 100644 src/primitives/bitmap.rs diff --git a/src/primitives/bitmap.rs b/src/primitives/bitmap.rs new file mode 100644 index 0000000..fdfb42e --- /dev/null +++ b/src/primitives/bitmap.rs @@ -0,0 +1,39 @@ +pub struct Bitmap { + map: T, +} + +impl Bitmap { + pub const fn new() -> Self { + Bitmap { map: T } + } + + /// Set the given bit to 1. + pub fn set_bit(&mut self, bit: usize) { + let mask = 1 << bit; + self.map |= mask; + } + + /// Clear the given bit. Set it to 0. + pub fn clear_bit(&mut self, bit: usize) { + let mask = 0 << bit; + self.map &= mask; + } + + /// Iterate over the bitmap and return the heavier bit. + /// Example: bitmap set to u8 -> map: 01001010. The function will return 6, because the first + /// bit set to 1, from the highest bit, is the bit 6. + /// Arguments: + /// &mut self: call the map initialized. Must be mutable. + pub fn find_leading_bit(&mut self) -> usize { + let bits = core::mem::size_of::() * 8; + let mut value: usize = 0; + for i in (0..bits).rev() { + let bit = (self.map >> i) & 1; + if bit == 1 { + value = i; + break; + } + } + value + } +} diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index 0aa67ee..0b6ad5c 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -1,2 +1,3 @@ +pub mod bitmap; pub mod ring_buff; pub mod stack; From 2330c850bbd78f0394ca175e5af6cae016c3dffc Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 15:28:56 +0100 Subject: [PATCH 44/84] feat(scheduler & task): update run_queue bitmap to use new Bitmap type, add new task public API to get task's priority --- src/scheduler/mod.rs | 27 ++++++++++++++++++--------- src/task/mod.rs | 4 ++++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index e91e553..7732693 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -20,26 +20,29 @@ use crate::{ config::{BLOCK_QUEUE_MAX_SIZE, CPU_CORE_NUMBER, RUN_QUEUE_MAX_SIZE, TASK_MAX_PRIORITY}, log, misc::{clear_reschedule, read_need_reschedule}, - primitives::ring_buff::RingBuffer, + primitives::{bitmap::Bitmap, ring_buff::RingBuffer}, task::{ TASK_HANDLER, TaskState, list::{task_list_get_idle_task, task_list_get_task_by_pid, task_list_update_task_by_pid}, - task_context_switch, task_pid, + task_context_switch, task_pid, task_priority, }, }; // Reflect the run queue state // Array of bitmaps, one bitmap per CPU core -pub static mut RUN_QUEUE_BITMAP: [u32; CPU_CORE_NUMBER] = [0u32; CPU_CORE_NUMBER]; -// Array of run queue per priority -// Each index of this array is a priority, and at each index, there's a ring buffer of all task +pub static mut RUN_QUEUE_BITMAP: [Bitmap; CPU_CORE_NUMBER] = + [const { Bitmap::new() }; CPU_CORE_NUMBER]; +// Array of Array of run queue per priority per CPU core. +// Each index of this array is specific a CPU core, index 0 is for CPU core 0, etc. +// Each index of the inside array is another array, each index is a priority. And at each index, there's a ring buffer of all task // with that priority. // We use the RUN_QUEUE_BITMAP to easily find the buffer with the highest priority to look into. pub static mut RUN_QUEUE: [[RingBuffer; TASK_MAX_PRIORITY]; CPU_CORE_NUMBER] = [[const { RingBuffer::init() }; TASK_MAX_PRIORITY]; CPU_CORE_NUMBER]; // Queue containing all blocked task. -pub static mut BLOCKED_QUEUE: [[RingBuffer; TASK_MAX_PRIORITY]; - CPU_CORE_NUMBER] = [[const { RingBuffer::init() }; TASK_MAX_PRIORITY]; CPU_CORE_NUMBER]; +// Same data structure as the RUN_QUEUE. +pub static mut BLOCKED_QUEUE: [RingBuffer; CPU_CORE_NUMBER] = + [const { RingBuffer::init() }; CPU_CORE_NUMBER]; /// Temporary function use to test the context switch and context restore on multiple task. /// Will certainly be used later on the real scheduler. @@ -48,16 +51,22 @@ pub static mut BLOCKED_QUEUE: [[RingBuffer; TASK_MAX_ /// Read on the RingBuffer to get the next task, update it, and update the RingBuffer. /// Not the best way to use the RingBuffer but it will do. #[unsafe(no_mangle)] -pub fn dispatch() { +pub fn scheduler(core: usize) { + #[allow(static_mut_refs)] + let current_run_queue = unsafe { RUN_QUEUE }[core]; + #[allow(static_mut_refs)] + let current_blocked_queue = unsafe { BLOCKED_QUEUE }[core]; // Current running task let mut current_task = unsafe { *TASK_HANDLER }; if current_task.state != TaskState::Blocked { current_task.state = TaskState::Ready; let pid = task_pid(¤t_task); + let priority = task_priority(¤t_task); task_list_update_task_by_pid(pid, current_task); #[allow(static_mut_refs)] unsafe { - RUN_QUEUE.push(pid) + // Push current task to the priority buffer + RUN_QUEUE[priority].push(pid) }; } let resched = read_need_reschedule(); diff --git a/src/task/mod.rs b/src/task/mod.rs index 93340bc..e939a8e 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -176,6 +176,10 @@ pub fn task_pid(task: &Task) -> u16 { task.pid } +pub fn task_priority(task: &Task) -> u8 { + task.priority +} + /// Create the idle task pub fn task_idle_task() { let task_name: &str = "Idle task"; From 4abb5c497bc94b0c7a8b955fc6db58c4caad7f99 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 12 Feb 2026 09:20:59 +0100 Subject: [PATCH 45/84] feat(primitives & scheduler): add is_bitmap_zero to bitmap data structure and use it in scheduler --- src/primitives/bitmap.rs | 4 ++++ src/scheduler/mod.rs | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/primitives/bitmap.rs b/src/primitives/bitmap.rs index fdfb42e..90d4cf4 100644 --- a/src/primitives/bitmap.rs +++ b/src/primitives/bitmap.rs @@ -36,4 +36,8 @@ impl Bitmap { } value } + + pub fn is_bitmap_zero(&self) -> bool { + self.map == 0 + } } diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index 7732693..fd4d208 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -56,6 +56,7 @@ pub fn scheduler(core: usize) { let current_run_queue = unsafe { RUN_QUEUE }[core]; #[allow(static_mut_refs)] let current_blocked_queue = unsafe { BLOCKED_QUEUE }[core]; + let current_run_queue_bitmap = unsafe { RUN_QUEUE_BITMAP }[core]; // Current running task let mut current_task = unsafe { *TASK_HANDLER }; if current_task.state != TaskState::Blocked { @@ -79,8 +80,8 @@ pub fn scheduler(core: usize) { } // Update and load next task #[allow(static_mut_refs)] - let get_next_task = unsafe { RUN_QUEUE.pop() }; - if get_next_task.is_none() { + let is_no_task = current_run_queue_bitmap.is_bitmap_zero(); + if is_no_task { log!( LogLevel::Debug, "No task available in the run queue, enter idle task." @@ -89,6 +90,8 @@ pub fn scheduler(core: usize) { #[allow(clippy::expect_used)] task_context_switch(idle.expect("ERROR: failed to get the idle task, invariant violated.")); } + let highest_priority = current_run_queue_bitmap.find_leading_bit(); + let get_next_task = unsafe { RUN_QUEUE[highest_priority].pop() }; // Allow unwrap because it's a temporary function #[allow(clippy::unwrap_used)] let next_task_pid = get_next_task.unwrap(); From f3c727327d71da06169292738218e2fd433dc06e Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 12 Feb 2026 10:36:19 +0100 Subject: [PATCH 46/84] feat(primitives & scheduler): update the bitmap and ring buffer primitive, add a new delta_list primitive and update in scheduler --- src/primitives/bitmap.rs | 9 +++++---- src/primitives/delta_list.rs | 22 ++++++++++++++++++++++ src/primitives/ring_buff.rs | 1 + src/scheduler/mod.rs | 29 +++++++++++++++-------------- 4 files changed, 43 insertions(+), 18 deletions(-) create mode 100644 src/primitives/delta_list.rs diff --git a/src/primitives/bitmap.rs b/src/primitives/bitmap.rs index 90d4cf4..a525dff 100644 --- a/src/primitives/bitmap.rs +++ b/src/primitives/bitmap.rs @@ -1,10 +1,11 @@ -pub struct Bitmap { - map: T, +#[derive(Copy, Clone)] +pub struct Bitmap { + map: u32, } -impl Bitmap { +impl Bitmap { pub const fn new() -> Self { - Bitmap { map: T } + Bitmap { map: 0 } } /// Set the given bit to 1. diff --git a/src/primitives/delta_list.rs b/src/primitives/delta_list.rs new file mode 100644 index 0000000..f64ebd0 --- /dev/null +++ b/src/primitives/delta_list.rs @@ -0,0 +1,22 @@ +pub struct DeltaList { + list: [DeltaItem; N], +} + +impl DeltaList { + pub const fn new() -> Self { + DeltaList { + list: [const { DeltaItem::new() }; N], + } + } +} + +struct DeltaItem { + id: usize, + delta: usize, +} + +impl DeltaItem { + pub const fn new() -> Self { + DeltaItem { id: 0, delta: 0 } + } +} diff --git a/src/primitives/ring_buff.rs b/src/primitives/ring_buff.rs index b2b54c7..d68dad9 100644 --- a/src/primitives/ring_buff.rs +++ b/src/primitives/ring_buff.rs @@ -23,6 +23,7 @@ References: use crate::{log, logs::LogLevel}; +#[derive(Copy, Clone)] pub struct RingBuffer { buff: [Option; N], // Oldest element in the buffer diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index fd4d208..b5dd321 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -16,7 +16,10 @@ References: use crate::{ LogLevel, - arch::scheduler::{SCHEDULER_CTX, SchedulerCtx, sched_ctx_restore}, + arch::{ + helpers::current_cpu_core, + scheduler::{SCHEDULER_CTX, SchedulerCtx, sched_ctx_restore}, + }, config::{BLOCK_QUEUE_MAX_SIZE, CPU_CORE_NUMBER, RUN_QUEUE_MAX_SIZE, TASK_MAX_PRIORITY}, log, misc::{clear_reschedule, read_need_reschedule}, @@ -30,7 +33,7 @@ use crate::{ // Reflect the run queue state // Array of bitmaps, one bitmap per CPU core -pub static mut RUN_QUEUE_BITMAP: [Bitmap; CPU_CORE_NUMBER] = +pub static mut RUN_QUEUE_BITMAP: [Bitmap; CPU_CORE_NUMBER] = [const { Bitmap::new() }; CPU_CORE_NUMBER]; // Array of Array of run queue per priority per CPU core. // Each index of this array is specific a CPU core, index 0 is for CPU core 0, etc. @@ -51,24 +54,22 @@ pub static mut BLOCKED_QUEUE: [RingBuffer; CPU_CORE_N /// Read on the RingBuffer to get the next task, update it, and update the RingBuffer. /// Not the best way to use the RingBuffer but it will do. #[unsafe(no_mangle)] -pub fn scheduler(core: usize) { +pub fn scheduler() { + let core: usize = current_cpu_core(); #[allow(static_mut_refs)] - let current_run_queue = unsafe { RUN_QUEUE }[core]; + let current_run_queue = &mut unsafe { RUN_QUEUE }[core]; #[allow(static_mut_refs)] - let current_blocked_queue = unsafe { BLOCKED_QUEUE }[core]; - let current_run_queue_bitmap = unsafe { RUN_QUEUE_BITMAP }[core]; + let current_blocked_queue = &mut unsafe { BLOCKED_QUEUE }[core]; + let current_run_queue_bitmap = &mut unsafe { RUN_QUEUE_BITMAP }[core]; // Current running task let mut current_task = unsafe { *TASK_HANDLER }; if current_task.state != TaskState::Blocked { current_task.state = TaskState::Ready; let pid = task_pid(¤t_task); - let priority = task_priority(¤t_task); + let priority: usize = task_priority(¤t_task).into(); task_list_update_task_by_pid(pid, current_task); - #[allow(static_mut_refs)] - unsafe { - // Push current task to the priority buffer - RUN_QUEUE[priority].push(pid) - }; + // Push current task to the priority buffer + current_run_queue[priority].push(pid) } let resched = read_need_reschedule(); if resched { @@ -90,8 +91,8 @@ pub fn scheduler(core: usize) { #[allow(clippy::expect_used)] task_context_switch(idle.expect("ERROR: failed to get the idle task, invariant violated.")); } - let highest_priority = current_run_queue_bitmap.find_leading_bit(); - let get_next_task = unsafe { RUN_QUEUE[highest_priority].pop() }; + let highest_priority: usize = current_run_queue_bitmap.find_leading_bit(); + let get_next_task = current_run_queue[highest_priority].pop(); // Allow unwrap because it's a temporary function #[allow(clippy::unwrap_used)] let next_task_pid = get_next_task.unwrap(); From 4da9773d8d693399b9edfcc8d518fb2138781dd6 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 12 Feb 2026 10:36:49 +0100 Subject: [PATCH 47/84] refactor: rename the old dispatch function to scheduler and update codebase --- src/arch/riscv32/asm/trap_entry.S | 2 +- src/arch/riscv32/asm/yield.S | 2 +- src/primitives/mod.rs | 1 + src/task/primitives.rs | 26 ++++++++++----------- src/tests/arch/riscv32/task/task_context.rs | 4 ++-- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/arch/riscv32/asm/trap_entry.S b/src/arch/riscv32/asm/trap_entry.S index 42d9249..529807d 100644 --- a/src/arch/riscv32/asm/trap_entry.S +++ b/src/arch/riscv32/asm/trap_entry.S @@ -56,7 +56,7 @@ trap_entry: bnez a0, 1f mret 1: - call dispatch + call scheduler mret # Function used when the trap strack = 0, just infinite loop for debuging purpose diff --git a/src/arch/riscv32/asm/yield.S b/src/arch/riscv32/asm/yield.S index e23cd0f..3f4d010 100644 --- a/src/arch/riscv32/asm/yield.S +++ b/src/arch/riscv32/asm/yield.S @@ -12,4 +12,4 @@ yield: # Call the save context function call save_context # Once save_context return, call the scheduler - call dispatch + call scheduler diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index 0b6ad5c..457bc0b 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -1,3 +1,4 @@ pub mod bitmap; +pub mod delta_list; pub mod ring_buff; pub mod stack; diff --git a/src/task/primitives.rs b/src/task/primitives.rs index fdbb991..f22ed10 100644 --- a/src/task/primitives.rs +++ b/src/task/primitives.rs @@ -23,7 +23,7 @@ use crate::{ log, logs::LogLevel, misc::need_reschedule, - scheduler::{BLOCKED_QUEUE, RUN_QUEUE, dispatch}, + scheduler::{BLOCKED_QUEUE, RUN_QUEUE, scheduler}, }; use super::{ @@ -49,7 +49,7 @@ fn task_set_wake_tick(tick: usize) { // Call task primitive to update current task state task_block_until(awake_tick); // Call a re-schedule - dispatch(); + scheduler(); } /// Block the current task until the given tick is reach. @@ -71,23 +71,21 @@ pub fn task_block_until(tick: usize) { // 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); - } } -/// Pop the oldest element in the blocked queue and check if the task can be awake. If not, repush -/// it to the blocked queue +/// Check all the blocked queue to find the task to awake. Just update the task that need to be +/// awake, make them ready. Don't handle the queue by itself. /// 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() }; + //TODO: use blocked queue size. or something idk + let size = 0; if size == 0 { return; } #[allow(static_mut_refs)] - let pid = unsafe { BLOCKED_QUEUE.pop() }; + //TODO: Use blocked queue + let pid = None; if pid.is_none() { log!(LogLevel::Error, "Error getting the oldest pid in run queue"); return; @@ -117,8 +115,8 @@ pub fn task_awake_blocked(tick: usize) { 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)] - RUN_QUEUE.push(pid.expect("Failed to get the pid behind the Option<>")); + // #[allow(clippy::expect_used)] + // RUN_QUEUE.push(pid.expect("Failed to get the pid behind the Option<>")); need_reschedule(); }; } else { @@ -127,8 +125,8 @@ pub fn task_awake_blocked(tick: usize) { 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<>")) + // #[allow(clippy::expect_used)] + // BLOCKED_QUEUE.push(pid.expect("Failed to get the pid behind the Option<>")) }; } } diff --git a/src/tests/arch/riscv32/task/task_context.rs b/src/tests/arch/riscv32/task/task_context.rs index e482183..ff7c891 100644 --- a/src/tests/arch/riscv32/task/task_context.rs +++ b/src/tests/arch/riscv32/task/task_context.rs @@ -4,7 +4,7 @@ use core::{mem, ptr}; use crate::{ arch::{scheduler::init_sched_ctx, task::task_context::TaskContext, traps::interrupt::halt}, - scheduler::dispatch, + scheduler::scheduler, task::{ CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, primitives::r#yield, task_context_switch, task_create, @@ -143,7 +143,7 @@ pub fn test_task_context_switch() -> u8 { test_info!( "The next output should be the task A and B, which print alternately A, and B, with a digit. The final output must be: from A: 31, and from B: 28" ); - init_sched_ctx(dispatch); + init_sched_ctx(scheduler); task_context_switch(task.unwrap()); 0 } From b12dd8f8ccba5d6a23e6378b627aaed24db9d684 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 12 Feb 2026 17:33:28 +0100 Subject: [PATCH 48/84] feat(primitives): working on the push to delta list, hard to do, wip --- src/primitives/delta_list.rs | 133 ++++++++++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 3 deletions(-) diff --git a/src/primitives/delta_list.rs b/src/primitives/delta_list.rs index f64ebd0..cff6b36 100644 --- a/src/primitives/delta_list.rs +++ b/src/primitives/delta_list.rs @@ -1,22 +1,149 @@ +use crate::log; + pub struct DeltaList { - list: [DeltaItem; N], + list: [Option; N], + head: usize, + tail: usize, + count: usize, } impl DeltaList { pub const fn new() -> Self { DeltaList { - list: [const { DeltaItem::new() }; N], + list: [None; N], + // Oldest node, the top of the linked list + head: 0, + // Newest node, the bottom of the linked list, doesn't have a node below it. + tail: 0, + count: 0, } } + + pub fn push(&mut self, current_tick: usize, id: usize, value: usize) { + // Get the size of the list + let size = self.size(); + // Compute delta of the new node + let mut delta = value - current_tick; + if size == self.list.len() { + log!(LogLevel::Warning, "The delta-list is full, abort push."); + return; + } + // If the list is empty, push the new node to index 0 of the list + if size == 0 { + self.list[0] = DeltaItem { + id, + delta, + next_node: None, + }; + self.head = 0; + return; + } + let mut current_node: usize = self.head; + let mut next_node: DeltaItem = DeltaItem { + id, + delta, + next_node: None, + }; + let available_index: usize = 0; + // Iterate to find an available index. + for i in 0..self.list.len() { + let find_available_index = self.list[i]; + if find_available_index.is_none() && available_index == 0 { + available_index = i; + } + } + let mut new_node = DeltaItem { + id, + delta, + next_node: None, + }; + let mut prev_node_ptr: Option = None; + // Iterate over the linked list to find the spot for the new node inside it. + for i in 0..self.list.len() { + // Allow expect, we get the size by iterating all over the list until reaching None. + // We shouldn't get a None value, unless something is wrong. So we want to fail-fast. + #[allow(clippy::expect_used)] + // Get current node + let mut node = self.list[current_node].expect("Failed to get node behind Option<>"); + if delta > node.delta { + // Compute delta by subtracting the current node delta + delta -= node.delta; + // If the current node is tail. + if node.next_node.is_none() { + // Update current node to point to the new tail(new_node) + node.next_node = available_index; + // Update current node in list + self.list[current_node] = Some(node); + // Update tail to point to the new_node + self.tail = available_index; + // Update new node delta to use correct one + new_node.delta = delta; + // Push new node to available index in list + self.list[available_index] = Some(new_node); + break; + } + // If node.next_node is some, continue to the next node. + prev_node_ptr = Some(current_node); + // Safe because we check if is_none before. + #[allow(clippy::unwrap_used)] + current_node = node.next_node.unwrap(); + continue; + // If delta is < to current node delta + } else { + // if prev_node_ptr.is_none() { + // break; + // } + // Update new node delta to use correct one + new_node.delta = delta; + // Update new node to point to the next node(the current node) + new_node.next_node = Some(current_node); + // Get the previous node and update it to point to the new node + let prev_node = + self.list[prev_node_ptr.expect("Failed to get the usize behind the Option<>")]; + if prev_node.is_none() { + log!( + LogLevel::Warning, + "The previous node in the delta-list is none. This shouldn't be possible." + ); + return; + } + prev_node.next_node = available_index; + self.list[prev_node_ptr] = Some(prev_node); + // Update current node delta + node.delta -= new_node.delta; + self.list[current_node] = Some(node); + self.list[available_index] = Some(new_node); + break; + } + } + } + + pub fn size(&self) -> usize { + let output: usize = 0; + for i in 0..self.list.len() { + if self.list[i].is_none() { + output = i; + break; + } + } + return output; + } } struct DeltaItem { id: usize, delta: usize, + // The node before this one. + // If this is None, then this node is the tail. + next_node: Option, } impl DeltaItem { pub const fn new() -> Self { - DeltaItem { id: 0, delta: 0 } + DeltaItem { + id: 0, + delta: 0, + next_node: 0, + } } } From f7f2bc1e111ee65ff258caed653293fa93eac748 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 12 Feb 2026 18:38:18 +0100 Subject: [PATCH 49/84] feat(primitives & tests): start adding DeltaList test, easier to test it and see if t's work(it doesn't) --- src/primitives/delta_list.rs | 104 ++++++++++++++++++++--------- src/tests/primitives/delta_list.rs | 44 ++++++++++++ src/tests/primitives/mod.rs | 1 + 3 files changed, 118 insertions(+), 31 deletions(-) create mode 100644 src/tests/primitives/delta_list.rs diff --git a/src/primitives/delta_list.rs b/src/primitives/delta_list.rs index cff6b36..680254b 100644 --- a/src/primitives/delta_list.rs +++ b/src/primitives/delta_list.rs @@ -1,5 +1,24 @@ +/* +File info: DeltaList primitive type. + +Test coverage: + +Tested: + +Not tested: + +Reasons: + +Tests files: +- 'src/tests/primitives/delta_list.rs' + +References: +*/ + +use crate::LogLevel; use crate::log; +#[derive(Clone, Copy, Debug)] pub struct DeltaList { list: [Option; N], head: usize, @@ -10,7 +29,7 @@ pub struct DeltaList { impl DeltaList { pub const fn new() -> Self { DeltaList { - list: [None; N], + list: [const { None }; N], // Oldest node, the top of the linked list head: 0, // Newest node, the bottom of the linked list, doesn't have a node below it. @@ -25,33 +44,36 @@ impl DeltaList { // Compute delta of the new node let mut delta = value - current_tick; if size == self.list.len() { - log!(LogLevel::Warning, "The delta-list is full, abort push."); + log!(LogLevel::Warn, "The delta-list is full, abort push."); return; } // If the list is empty, push the new node to index 0 of the list if size == 0 { - self.list[0] = DeltaItem { + self.list[0] = Some(DeltaItem { id, delta, next_node: None, - }; + }); self.head = 0; return; } let mut current_node: usize = self.head; - let mut next_node: DeltaItem = DeltaItem { - id, - delta, - next_node: None, - }; - let available_index: usize = 0; + let mut available_index: Option = None; // Iterate to find an available index. for i in 0..self.list.len() { - let find_available_index = self.list[i]; - if find_available_index.is_none() && available_index == 0 { - available_index = i; + let find_available_index = &self.list[i]; + if find_available_index.is_none() && available_index.is_none() { + available_index = Some(i); } } + if available_index.is_none() { + log!( + LogLevel::Error, + "The delta-list is full, abort push. Consider increasing the blocked queue size." + ); + return; + } + self.count += 1; let mut new_node = DeltaItem { id, delta, @@ -62,7 +84,6 @@ impl DeltaList { for i in 0..self.list.len() { // Allow expect, we get the size by iterating all over the list until reaching None. // We shouldn't get a None value, unless something is wrong. So we want to fail-fast. - #[allow(clippy::expect_used)] // Get current node let mut node = self.list[current_node].expect("Failed to get node behind Option<>"); if delta > node.delta { @@ -75,51 +96,65 @@ impl DeltaList { // Update current node in list self.list[current_node] = Some(node); // Update tail to point to the new_node - self.tail = available_index; + self.tail = available_index.expect("Failed to get the usize behind the Option<>. Maybe there's isn't available space in the delta-list."); // Update new node delta to use correct one new_node.delta = delta; // Push new node to available index in list - self.list[available_index] = Some(new_node); + self.list[available_index.unwrap()] = Some(new_node); break; } // If node.next_node is some, continue to the next node. prev_node_ptr = Some(current_node); // Safe because we check if is_none before. #[allow(clippy::unwrap_used)] - current_node = node.next_node.unwrap(); + let node_next_node = node.next_node.unwrap(); + current_node = node_next_node; continue; // If delta is < to current node delta } else { - // if prev_node_ptr.is_none() { - // break; - // } + if prev_node_ptr.is_none() { + #[allow(clippy::expect_used)] + let mut old_head = + self.list[self.head].expect("Failed to get the delta-list head node."); + old_head.delta -= delta; + self.list[self.head] = Some(old_head); + new_node.next_node = Some(self.head); + new_node.delta = delta; + self.head = available_index.expect("Available index should not be None."); + self.list[self.head] = Some(new_node); + break; + } // Update new node delta to use correct one new_node.delta = delta; // Update new node to point to the next node(the current node) new_node.next_node = Some(current_node); // Get the previous node and update it to point to the new node - let prev_node = + let mut prev_node = self.list[prev_node_ptr.expect("Failed to get the usize behind the Option<>")]; if prev_node.is_none() { log!( - LogLevel::Warning, + LogLevel::Warn, "The previous node in the delta-list is none. This shouldn't be possible." ); return; } - prev_node.next_node = available_index; - self.list[prev_node_ptr] = Some(prev_node); + prev_node + .expect("Previous node should not be None") + .next_node = available_index; + self.list[prev_node_ptr.expect("Failed to get the usize behind the Option<>")] = + prev_node; // Update current node delta node.delta -= new_node.delta; self.list[current_node] = Some(node); - self.list[available_index] = Some(new_node); + self.list[available_index.expect("Available index should not be None")] = + Some(new_node); break; } } } pub fn size(&self) -> usize { - let output: usize = 0; + let mut output: usize = 0; for i in 0..self.list.len() { if self.list[i].is_none() { output = i; @@ -128,14 +163,21 @@ impl DeltaList { } return output; } + + #[cfg(feature = "test")] + pub fn get_index(&self, idx: usize) -> DeltaItem { + // Unwrap directly because it's in test env. + self.list[idx].unwrap() + } } -struct DeltaItem { - id: usize, - delta: usize, +#[derive(Clone, Copy, Debug)] +pub struct DeltaItem { + pub id: usize, + pub delta: usize, // The node before this one. // If this is None, then this node is the tail. - next_node: Option, + pub next_node: Option, } impl DeltaItem { @@ -143,7 +185,7 @@ impl DeltaItem { DeltaItem { id: 0, delta: 0, - next_node: 0, + next_node: None, } } } diff --git a/src/tests/primitives/delta_list.rs b/src/tests/primitives/delta_list.rs new file mode 100644 index 0000000..070e83b --- /dev/null +++ b/src/tests/primitives/delta_list.rs @@ -0,0 +1,44 @@ +use crate::{ + kprint_fmt, + primitives::delta_list::DeltaList, + test_failed, test_info, + tests::{TEST_MANAGER, TestBehavior, TestCase, TestSuite, TestSuiteBehavior}, +}; + +fn test_delta_list_push() -> u8 { + let mut list: DeltaList<10> = DeltaList::new(); + // Push a short task + list.push(50, 1, 70); + list.push(50, 2, 80); + list.push(50, 3, 75); + kprint_fmt!("debug list: {:?}\n", list); + let first_node = list.get_index(0); + if first_node.id != 1 { + test_failed!("first node should be the task 1, got: {}\n", first_node.id); + return 1; + } + if first_node.next_node.unwrap() != 2 { + test_failed!( + "first node.next_node should be the task 3 at index 2, got: {}\n", + first_node.next_node.unwrap() + ); + return 1; + } + 0 +} + +pub fn delta_list_primitive_test_suite() { + const DELTA_LIST_TEST_SUITE: TestSuite = TestSuite { + tests: &[TestCase::init( + "DeltaList push", + test_delta_list_push, + TestBehavior::Default, + )], + name: "DeltaList primitive type", + behavior: TestSuiteBehavior::Default, + }; + #[allow(static_mut_refs)] + unsafe { + TEST_MANAGER.add_suite(&DELTA_LIST_TEST_SUITE) + }; +} diff --git a/src/tests/primitives/mod.rs b/src/tests/primitives/mod.rs index 38dd7d7..c9012fc 100644 --- a/src/tests/primitives/mod.rs +++ b/src/tests/primitives/mod.rs @@ -1 +1,2 @@ pub mod ring_buff; +pub mod delta_list; From 2c39d50504f1ea7e364cf4754922795ad03bba1a Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Fri, 13 Feb 2026 09:15:53 +0100 Subject: [PATCH 50/84] refactor: update the delta list to index_linked_list, delta was too much pain for not much gain --- .../{delta_list.rs => indexed_linked_list.rs} | 94 +++++++++---------- src/primitives/mod.rs | 2 +- .../{delta_list.rs => indexed_linked_list.rs} | 14 +-- src/tests/primitives/mod.rs | 3 +- 4 files changed, 52 insertions(+), 61 deletions(-) rename src/primitives/{delta_list.rs => indexed_linked_list.rs} (61%) rename src/tests/primitives/{delta_list.rs => indexed_linked_list.rs} (78%) diff --git a/src/primitives/delta_list.rs b/src/primitives/indexed_linked_list.rs similarity index 61% rename from src/primitives/delta_list.rs rename to src/primitives/indexed_linked_list.rs index 680254b..4ff46f9 100644 --- a/src/primitives/delta_list.rs +++ b/src/primitives/indexed_linked_list.rs @@ -1,5 +1,5 @@ /* -File info: DeltaList primitive type. +File info: IndexedLinkedList primitive type. Test coverage: @@ -19,16 +19,16 @@ use crate::LogLevel; use crate::log; #[derive(Clone, Copy, Debug)] -pub struct DeltaList { - list: [Option; N], +pub struct IndexedLinkedList { + list: [Option; N], head: usize, tail: usize, count: usize, } -impl DeltaList { +impl IndexedLinkedList { pub const fn new() -> Self { - DeltaList { + IndexedLinkedList { list: [const { None }; N], // Oldest node, the top of the linked list head: 0, @@ -38,20 +38,18 @@ impl DeltaList { } } - pub fn push(&mut self, current_tick: usize, id: usize, value: usize) { + pub fn push(&mut self, id: usize, value: usize) { // Get the size of the list let size = self.size(); - // Compute delta of the new node - let mut delta = value - current_tick; if size == self.list.len() { log!(LogLevel::Warn, "The delta-list is full, abort push."); return; } // If the list is empty, push the new node to index 0 of the list if size == 0 { - self.list[0] = Some(DeltaItem { + self.list[0] = Some(IndexedLinkedListNode { id, - delta, + value, next_node: None, }); self.head = 0; @@ -74,63 +72,55 @@ impl DeltaList { return; } self.count += 1; - let mut new_node = DeltaItem { + let mut new_node = IndexedLinkedListNode { id, - delta, + value, next_node: None, }; let mut prev_node_ptr: Option = None; - // Iterate over the linked list to find the spot for the new node inside it. - for i in 0..self.list.len() { - // Allow expect, we get the size by iterating all over the list until reaching None. - // We shouldn't get a None value, unless something is wrong. So we want to fail-fast. - // Get current node - let mut node = self.list[current_node].expect("Failed to get node behind Option<>"); - if delta > node.delta { - // Compute delta by subtracting the current node delta - delta -= node.delta; - // If the current node is tail. + for _ in 0..self.list.len() { + let mut node = self.list[current_node].expect("Failed to get the current node. This shouldn't be possible unless the linked list is corrupted."); + // If the current value is superior than the current node value, continue, or check the + // next_node. + if value > node.value { if node.next_node.is_none() { - // Update current node to point to the new tail(new_node) node.next_node = available_index; // Update current node in list self.list[current_node] = Some(node); - // Update tail to point to the new_node self.tail = available_index.expect("Failed to get the usize behind the Option<>. Maybe there's isn't available space in the delta-list."); - // Update new node delta to use correct one - new_node.delta = delta; // Push new node to available index in list self.list[available_index.unwrap()] = Some(new_node); break; } - // If node.next_node is some, continue to the next node. prev_node_ptr = Some(current_node); - // Safe because we check if is_none before. - #[allow(clippy::unwrap_used)] - let node_next_node = node.next_node.unwrap(); + let node_next_node = node + .next_node + .expect("Failed to get the next_node behind the Option<>"); current_node = node_next_node; continue; - // If delta is < to current node delta + // Else if the current value is not superior, update the list to push the new_node + // before the current one. } else { + // If there's no previous node, than we are at the head, so update the head to + // point to the new node. if prev_node_ptr.is_none() { - #[allow(clippy::expect_used)] - let mut old_head = - self.list[self.head].expect("Failed to get the delta-list head node."); - old_head.delta -= delta; - self.list[self.head] = Some(old_head); - new_node.next_node = Some(self.head); - new_node.delta = delta; - self.head = available_index.expect("Available index should not be None."); + // Get the previous head + let prev_head = self.head; + // Update the head to point to the new node + self.head = available_index + .expect("Failed to get the available_index behind the Option<>"); + // Update the new_node to point to the old head + new_node.next_node = Some(prev_head); + // Update list to push new_node to head self.list[self.head] = Some(new_node); break; } - // Update new node delta to use correct one - new_node.delta = delta; - // Update new node to point to the next node(the current node) + // If there's a previous node. new_node.next_node = Some(current_node); - // Get the previous node and update it to point to the new node + // Get the previous node let mut prev_node = self.list[prev_node_ptr.expect("Failed to get the usize behind the Option<>")]; + // Check to see if there's an error getting the previous node if prev_node.is_none() { log!( LogLevel::Warn, @@ -138,14 +128,14 @@ impl DeltaList { ); return; } + // Update previous node to point to the new node prev_node .expect("Previous node should not be None") .next_node = available_index; + // Update the previous node in the list self.list[prev_node_ptr.expect("Failed to get the usize behind the Option<>")] = prev_node; - // Update current node delta - node.delta -= new_node.delta; - self.list[current_node] = Some(node); + // Push the new node to the list self.list[available_index.expect("Available index should not be None")] = Some(new_node); break; @@ -165,26 +155,26 @@ impl DeltaList { } #[cfg(feature = "test")] - pub fn get_index(&self, idx: usize) -> DeltaItem { + pub fn get_index(&self, idx: usize) -> IndexedLinkedListNode { // Unwrap directly because it's in test env. self.list[idx].unwrap() } } #[derive(Clone, Copy, Debug)] -pub struct DeltaItem { +pub struct IndexedLinkedListNode { pub id: usize, - pub delta: usize, + pub value: usize, // The node before this one. // If this is None, then this node is the tail. pub next_node: Option, } -impl DeltaItem { +impl IndexedLinkedListNode { pub const fn new() -> Self { - DeltaItem { + IndexedLinkedListNode { id: 0, - delta: 0, + value: 0, next_node: None, } } diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index 457bc0b..11a2a5f 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -1,4 +1,4 @@ pub mod bitmap; -pub mod delta_list; +pub mod indexed_linked_list; pub mod ring_buff; pub mod stack; diff --git a/src/tests/primitives/delta_list.rs b/src/tests/primitives/indexed_linked_list.rs similarity index 78% rename from src/tests/primitives/delta_list.rs rename to src/tests/primitives/indexed_linked_list.rs index 070e83b..1f5abdb 100644 --- a/src/tests/primitives/delta_list.rs +++ b/src/tests/primitives/indexed_linked_list.rs @@ -1,16 +1,16 @@ use crate::{ kprint_fmt, - primitives::delta_list::DeltaList, + primitives::indexed_linked_list::IndexedLinkedList, test_failed, test_info, tests::{TEST_MANAGER, TestBehavior, TestCase, TestSuite, TestSuiteBehavior}, }; fn test_delta_list_push() -> u8 { - let mut list: DeltaList<10> = DeltaList::new(); + let mut list: IndexedLinkedList<10> = IndexedLinkedList::new(); // Push a short task - list.push(50, 1, 70); - list.push(50, 2, 80); - list.push(50, 3, 75); + list.push(1, 70); + list.push(2, 80); + list.push(3, 75); kprint_fmt!("debug list: {:?}\n", list); let first_node = list.get_index(0); if first_node.id != 1 { @@ -30,11 +30,11 @@ fn test_delta_list_push() -> u8 { pub fn delta_list_primitive_test_suite() { const DELTA_LIST_TEST_SUITE: TestSuite = TestSuite { tests: &[TestCase::init( - "DeltaList push", + "IndexedLinkedList push", test_delta_list_push, TestBehavior::Default, )], - name: "DeltaList primitive type", + name: "IndexedLinkedList primitive type", behavior: TestSuiteBehavior::Default, }; #[allow(static_mut_refs)] diff --git a/src/tests/primitives/mod.rs b/src/tests/primitives/mod.rs index c9012fc..77a26d1 100644 --- a/src/tests/primitives/mod.rs +++ b/src/tests/primitives/mod.rs @@ -1,2 +1,3 @@ +pub mod indexed_linked_list; pub mod ring_buff; -pub mod delta_list; + From b3aa4f7e92182fa3ef6776316548099addc5e9ee Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Fri, 13 Feb 2026 10:17:53 +0100 Subject: [PATCH 51/84] feat(primitives & tests): make the indexed linked list work --- src/primitives/indexed_linked_list.rs | 37 ++++++++++----------- src/tests/primitives/indexed_linked_list.rs | 1 - 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/primitives/indexed_linked_list.rs b/src/primitives/indexed_linked_list.rs index 4ff46f9..e925b8a 100644 --- a/src/primitives/indexed_linked_list.rs +++ b/src/primitives/indexed_linked_list.rs @@ -53,6 +53,7 @@ impl IndexedLinkedList { next_node: None, }); self.head = 0; + self.count += 1; return; } let mut current_node: usize = self.head; @@ -71,7 +72,6 @@ impl IndexedLinkedList { ); return; } - self.count += 1; let mut new_node = IndexedLinkedListNode { id, value, @@ -79,14 +79,15 @@ impl IndexedLinkedList { }; let mut prev_node_ptr: Option = None; for _ in 0..self.list.len() { - let mut node = self.list[current_node].expect("Failed to get the current node. This shouldn't be possible unless the linked list is corrupted."); + let node: &mut IndexedLinkedListNode = self + .get_node(current_node) + .expect("Failed to get the asked node, linked list may be empty or corrupted"); // If the current value is superior than the current node value, continue, or check the // next_node. if value > node.value { if node.next_node.is_none() { node.next_node = available_index; // Update current node in list - self.list[current_node] = Some(node); self.tail = available_index.expect("Failed to get the usize behind the Option<>. Maybe there's isn't available space in the delta-list."); // Push new node to available index in list self.list[available_index.unwrap()] = Some(new_node); @@ -118,29 +119,27 @@ impl IndexedLinkedList { // If there's a previous node. new_node.next_node = Some(current_node); // Get the previous node - let mut prev_node = - self.list[prev_node_ptr.expect("Failed to get the usize behind the Option<>")]; - // Check to see if there's an error getting the previous node - if prev_node.is_none() { - log!( - LogLevel::Warn, - "The previous node in the delta-list is none. This shouldn't be possible." - ); - return; - } + let prev_node: &mut IndexedLinkedListNode = self + .get_node(prev_node_ptr.expect("Failed to get the previous_node index behind Option<>, linked-list may be corrupted")) + .expect("Failed to get the asked node, linked list may be empty or corrupted"); // Update previous node to point to the new node - prev_node - .expect("Previous node should not be None") - .next_node = available_index; - // Update the previous node in the list - self.list[prev_node_ptr.expect("Failed to get the usize behind the Option<>")] = - prev_node; + prev_node.next_node = available_index; // Push the new node to the list self.list[available_index.expect("Available index should not be None")] = Some(new_node); break; } } + self.count += 1; + } + + pub fn get_node(&mut self, idx: usize) -> Option<&mut IndexedLinkedListNode> { + let node = self.list[idx].as_mut(); + if let Some(is_node) = node { + return Some(is_node); + } else { + return None; + } } pub fn size(&self) -> usize { diff --git a/src/tests/primitives/indexed_linked_list.rs b/src/tests/primitives/indexed_linked_list.rs index 1f5abdb..bf867aa 100644 --- a/src/tests/primitives/indexed_linked_list.rs +++ b/src/tests/primitives/indexed_linked_list.rs @@ -11,7 +11,6 @@ fn test_delta_list_push() -> u8 { list.push(1, 70); list.push(2, 80); list.push(3, 75); - kprint_fmt!("debug list: {:?}\n", list); let first_node = list.get_index(0); if first_node.id != 1 { test_failed!("first node should be the task 1, got: {}\n", first_node.id); From d21932199be8f2558c524e9aa8ada83626854aef Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Fri, 13 Feb 2026 10:45:36 +0100 Subject: [PATCH 52/84] feat(primitives & tests): add more test for the indexed linked list push --- src/primitives/indexed_linked_list.rs | 14 ++++++++ src/tests/primitives/indexed_linked_list.rs | 36 +++++++++++++++++---- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/primitives/indexed_linked_list.rs b/src/primitives/indexed_linked_list.rs index e925b8a..35308f4 100644 --- a/src/primitives/indexed_linked_list.rs +++ b/src/primitives/indexed_linked_list.rs @@ -153,11 +153,25 @@ impl IndexedLinkedList { return output; } + pub fn get_count(&self) -> usize { + self.count + } + #[cfg(feature = "test")] pub fn get_index(&self, idx: usize) -> IndexedLinkedListNode { // Unwrap directly because it's in test env. self.list[idx].unwrap() } + + #[cfg(feature = "test")] + pub fn get_head(&self) -> usize { + self.head + } + + #[cfg(feature = "test")] + pub fn get_tail(&self) -> usize { + self.tail + } } #[derive(Clone, Copy, Debug)] diff --git a/src/tests/primitives/indexed_linked_list.rs b/src/tests/primitives/indexed_linked_list.rs index bf867aa..4ccdb1b 100644 --- a/src/tests/primitives/indexed_linked_list.rs +++ b/src/tests/primitives/indexed_linked_list.rs @@ -7,22 +7,44 @@ use crate::{ fn test_delta_list_push() -> u8 { let mut list: IndexedLinkedList<10> = IndexedLinkedList::new(); - // Push a short task + // Push some task list.push(1, 70); list.push(2, 80); list.push(3, 75); - let first_node = list.get_index(0); - if first_node.id != 1 { - test_failed!("first node should be the task 1, got: {}\n", first_node.id); + list.push(4, 50); + // Get head and tail task + let head = list.get_head(); + let tail = list.get_tail(); + let head_node = list.get_index(head); + let tail_node = list.get_index(tail); + if head_node.id != 4 { + test_failed!("head node should be the task 4, got: {}\n", head_node.id); return 1; } - if first_node.next_node.unwrap() != 2 { + if head_node.next_node.unwrap() != 0 { test_failed!( - "first node.next_node should be the task 3 at index 2, got: {}\n", - first_node.next_node.unwrap() + "head node.next_node should be the task 1 at index 0, got: {}\n", + head_node.next_node.unwrap() ); return 1; } + if tail_node.id != 2 { + test_failed!("tail node should be the task 2, got: {}\n", tail_node.id); + return 1; + } + if tail_node.next_node.is_some() { + test_failed!( + "tail node should not have a next ask, got: {}\n", + tail_node.next_node.unwrap() + ); + return 1; + } + // Get number of node in the list + let count = list.get_count(); + if count != 4 { + test_failed!("count should be 4, got: {}\n", count); + return 1; + } 0 } From 8a4a38c5ef00919778e51ff8913ba19b2436c79b Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Fri, 13 Feb 2026 11:40:43 +0100 Subject: [PATCH 53/84] feat(primitives & tests): add pop and get_head_node methods with tests for the indexed_linked_list --- src/primitives/indexed_linked_list.rs | 22 +++++++ src/tests/primitives/indexed_linked_list.rs | 72 ++++++++++++++++++--- 2 files changed, 84 insertions(+), 10 deletions(-) diff --git a/src/primitives/indexed_linked_list.rs b/src/primitives/indexed_linked_list.rs index 35308f4..1ef6fbd 100644 --- a/src/primitives/indexed_linked_list.rs +++ b/src/primitives/indexed_linked_list.rs @@ -38,6 +38,7 @@ impl IndexedLinkedList { } } + /// Push the new node in the linked list. Can update the current node in it. pub fn push(&mut self, id: usize, value: usize) { // Get the size of the list let size = self.size(); @@ -133,6 +134,27 @@ impl IndexedLinkedList { self.count += 1; } + /// Remove the node at the head and return a mutable reference to it. + /// Update the linked list head to point to the next node. + pub fn pop(&mut self) -> Option<&mut IndexedLinkedListNode> { + let head = self.head; + let head_next_node = { + let head_node = self.get_node(head).expect("Failed to get the node."); + head_node.next_node + }; + if head_next_node.is_none() { + self.head = 0; + } else { + self.head = head_next_node + .expect("Failed to get the usize behind the Option<> in node.next_node"); + } + self.get_node(head) + } + + pub fn get_head_node(&self) -> Option<&IndexedLinkedListNode> { + self.list[self.head].as_ref() + } + pub fn get_node(&mut self, idx: usize) -> Option<&mut IndexedLinkedListNode> { let node = self.list[idx].as_mut(); if let Some(is_node) = node { diff --git a/src/tests/primitives/indexed_linked_list.rs b/src/tests/primitives/indexed_linked_list.rs index 4ccdb1b..b711c80 100644 --- a/src/tests/primitives/indexed_linked_list.rs +++ b/src/tests/primitives/indexed_linked_list.rs @@ -5,7 +5,7 @@ use crate::{ tests::{TEST_MANAGER, TestBehavior, TestCase, TestSuite, TestSuiteBehavior}, }; -fn test_delta_list_push() -> u8 { +fn test_indexed_linked_list_push() -> u8 { let mut list: IndexedLinkedList<10> = IndexedLinkedList::new(); // Push some task list.push(1, 70); @@ -23,7 +23,7 @@ fn test_delta_list_push() -> u8 { } if head_node.next_node.unwrap() != 0 { test_failed!( - "head node.next_node should be the task 1 at index 0, got: {}\n", + "head node.next_node should be the task 1, got: {}\n", head_node.next_node.unwrap() ); return 1; @@ -48,18 +48,70 @@ fn test_delta_list_push() -> u8 { 0 } -pub fn delta_list_primitive_test_suite() { - const DELTA_LIST_TEST_SUITE: TestSuite = TestSuite { - tests: &[TestCase::init( - "IndexedLinkedList push", - test_delta_list_push, - TestBehavior::Default, - )], +fn test_indexed_linked_list_get_head_node() -> u8 { + let mut list: IndexedLinkedList<10> = IndexedLinkedList::new(); + // Push some task + list.push(1, 70); + list.push(2, 80); + list.push(3, 75); + let head = list.get_head_node().unwrap(); + if head.id != 1 { + test_failed!( + "head node.next_node should be the task 1, got: {}\n", + head.next_node.unwrap() + ); + return 1; + } + list.push(4, 50); + let head = list.get_head_node().unwrap(); + if head.id != 4 { + test_failed!( + "head node.next_node should be the task 4, got: {}\n", + head.next_node.unwrap() + ); + return 1; + } + 0 +} + +fn test_indexed_linked_list_pop() -> u8 { + let mut list: IndexedLinkedList<10> = IndexedLinkedList::new(); + // Push some task + list.push(1, 70); + list.push(2, 80); + list.push(3, 75); + let head = list.pop().unwrap(); + if head.id != 1 { + test_failed!("head node should be the task 1, got: {}\n", head.id); + return 1; + } + let head = list.pop().unwrap(); + if head.id != 3 { + test_failed!("head node should be the task 3, got: {}\n", head.id); + return 1; + } + 0 +} + +pub fn indexed_linked_list_primitive_test_suite() { + const INDEXED_LINKED_LIST_TEST_SUITE: TestSuite = TestSuite { + tests: &[ + TestCase::init( + "IndexedLinkedList push", + test_indexed_linked_list_push, + TestBehavior::Default, + ), + TestCase::init( + "IndexedLinkedList get_head_node", + test_indexed_linked_list_get_head_node, + TestBehavior::Default, + ), + ], name: "IndexedLinkedList primitive type", behavior: TestSuiteBehavior::Default, }; #[allow(static_mut_refs)] unsafe { - TEST_MANAGER.add_suite(&DELTA_LIST_TEST_SUITE) + TEST_MANAGER.add_suite(&INDEXED_LINKED_LIST_TEST_SUITE) }; } From 613c47ed86f046cd6d356c276d7d1baf430008b1 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Fri, 13 Feb 2026 11:53:35 +0100 Subject: [PATCH 54/84] docs(kernel): update the data structure doc to include the new IndexedLinkedList --- Documentation/kernel/data_structure.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Documentation/kernel/data_structure.md b/Documentation/kernel/data_structure.md index c306dd9..3dbc782 100644 --- a/Documentation/kernel/data_structure.md +++ b/Documentation/kernel/data_structure.md @@ -7,6 +7,8 @@ - [Invariants](#invariants) - [AlignedStack16](#alignedstack16) - [Invariants](#invariants-1) + - [IndexedLinkedList](#indexedlinkedlist) + - [Invariants](#invariants-2) ## Description @@ -37,3 +39,18 @@ This type is used when you need a stack on a buffer, and the `sp` must be aligne - 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. + +### IndexedLinkedList + +A linked list but store in an array, so each node is accessed from it's index in the array. +This is better to use this data structure as a side storage, like we use it to store blocked task. +Task are already store in a TaskList, so in the IndexedLinkedList used for blocked task we only store task id, and task awake tick for now. + +#### Invariants + +- All node should be accessible from the head node. +- The list is sorted naturally from the `value` field of a node. +- The `count` field should reflect the number of accessible node in the list. +- The list is empty when `count`, `head` and `tail` are equal to 0. +- If the `next_node` of a node is some, this `next_node` is valid. +- If the `next_node` of a node is none, then this node is the `tail`. From b5165f66bf9cff359c48988c8ab0be39bcfd446c Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Fri, 13 Feb 2026 14:53:19 +0100 Subject: [PATCH 55/84] feat(primitives & tests): add a duplication check --- src/primitives/indexed_linked_list.rs | 25 +++++++++++++++++++++ src/tests/primitives/indexed_linked_list.rs | 7 ++++++ 2 files changed, 32 insertions(+) diff --git a/src/primitives/indexed_linked_list.rs b/src/primitives/indexed_linked_list.rs index 1ef6fbd..adcdbe2 100644 --- a/src/primitives/indexed_linked_list.rs +++ b/src/primitives/indexed_linked_list.rs @@ -59,6 +59,31 @@ impl IndexedLinkedList { } let mut current_node: usize = self.head; let mut available_index: Option = None; + // Check if there's no id duplication possible by iterating over the linked list + { + let mut current_node: usize = self.head; + for _ in 0..self.list.len() { + let node = self + .get_node(current_node) + .expect("Failed to get the asked node, linked list may be empty or corrupted."); + if node.id == id { + log!( + LogLevel::Warn, + "The delta-list is full, abort push. Consider increasing the blocked queue size." + ); + return; + } else { + if node.next_node.is_none() { + break; + } else { + current_node = node + .next_node + .expect("Failed to get the next_node index behind the Option<>"); + continue; + } + } + } + } // Iterate to find an available index. for i in 0..self.list.len() { let find_available_index = &self.list[i]; diff --git a/src/tests/primitives/indexed_linked_list.rs b/src/tests/primitives/indexed_linked_list.rs index b711c80..68452e9 100644 --- a/src/tests/primitives/indexed_linked_list.rs +++ b/src/tests/primitives/indexed_linked_list.rs @@ -45,6 +45,13 @@ fn test_indexed_linked_list_push() -> u8 { test_failed!("count should be 4, got: {}\n", count); return 1; } + // Check duplication security + list.push(4, 80); + let count = list.get_count(); + if count != 4 { + test_failed!("count should be 4, got: {}\n", count); + return 1; + } 0 } From fb8bf47d6050bcc76cafd279f0e31efd75b2978e Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Fri, 13 Feb 2026 15:08:54 +0100 Subject: [PATCH 56/84] docs: update the docs for IndexedLinkedList in data structure --- Documentation/kernel/data_structure.md | 1 + src/primitives/indexed_linked_list.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/Documentation/kernel/data_structure.md b/Documentation/kernel/data_structure.md index 3dbc782..f135c7d 100644 --- a/Documentation/kernel/data_structure.md +++ b/Documentation/kernel/data_structure.md @@ -54,3 +54,4 @@ Task are already store in a TaskList, so in the IndexedLinkedList used for block - The list is empty when `count`, `head` and `tail` are equal to 0. - If the `next_node` of a node is some, this `next_node` is valid. - If the `next_node` of a node is none, then this node is the `tail`. +- The node `id` is unique, you can't add the same `id` in the list. diff --git a/src/primitives/indexed_linked_list.rs b/src/primitives/indexed_linked_list.rs index adcdbe2..2674ca4 100644 --- a/src/primitives/indexed_linked_list.rs +++ b/src/primitives/indexed_linked_list.rs @@ -39,6 +39,7 @@ impl IndexedLinkedList { } /// Push the new node in the linked list. Can update the current node in it. + /// Avoid duplication on id. The id is unique in the list. pub fn push(&mut self, id: usize, value: usize) { // Get the size of the list let size = self.size(); From 1a7cd0da96a484e16aca5716b439225229fe8deb Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 16 Feb 2026 09:55:39 +0100 Subject: [PATCH 57/84] feat(scheduler & task primitives): update the scheduler to use new blocked queue, update task primitive as well The task primitive now use correctly the new blocked queue, they don't touch the queues directly, just trigger a reschedule if needed. The scheduler will check the blocked queue and awake the head if a need_reschedule has been trigger. --- src/scheduler/mod.rs | 57 +++++++++++++++++++++++++++++++++--------- src/task/mod.rs | 11 ++++++++ src/task/primitives.rs | 44 +++++++++++++++++--------------- 3 files changed, 80 insertions(+), 32 deletions(-) diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index b5dd321..1022329 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -23,11 +23,11 @@ use crate::{ config::{BLOCK_QUEUE_MAX_SIZE, CPU_CORE_NUMBER, RUN_QUEUE_MAX_SIZE, TASK_MAX_PRIORITY}, log, misc::{clear_reschedule, read_need_reschedule}, - primitives::{bitmap::Bitmap, ring_buff::RingBuffer}, + primitives::{bitmap::Bitmap, indexed_linked_list::IndexedLinkedList, ring_buff::RingBuffer}, task::{ TASK_HANDLER, TaskState, list::{task_list_get_idle_task, task_list_get_task_by_pid, task_list_update_task_by_pid}, - task_context_switch, task_pid, task_priority, + task_awake_block_control, task_awake_tick, task_context_switch, task_pid, task_priority, }, }; @@ -44,8 +44,8 @@ pub static mut RUN_QUEUE: [[RingBuffer; TASK_MAX_PRIORI CPU_CORE_NUMBER] = [[const { RingBuffer::init() }; TASK_MAX_PRIORITY]; CPU_CORE_NUMBER]; // Queue containing all blocked task. // Same data structure as the RUN_QUEUE. -pub static mut BLOCKED_QUEUE: [RingBuffer; CPU_CORE_NUMBER] = - [const { RingBuffer::init() }; CPU_CORE_NUMBER]; +pub static mut BLOCKED_QUEUE: [IndexedLinkedList; CPU_CORE_NUMBER] = + [const { IndexedLinkedList::new() }; CPU_CORE_NUMBER]; /// Temporary function use to test the context switch and context restore on multiple task. /// Will certainly be used later on the real scheduler. @@ -61,8 +61,48 @@ pub fn scheduler() { #[allow(static_mut_refs)] let current_blocked_queue = &mut unsafe { BLOCKED_QUEUE }[core]; let current_run_queue_bitmap = &mut unsafe { RUN_QUEUE_BITMAP }[core]; + // Check the need_reschedule flag + let resched = read_need_reschedule(); + if !resched { + // Get the current task and context switch on it. + return; + } else { + log!( + LogLevel::Debug, + "Reschedule needed, updating queues, clearing the need reschedule bit." + ); + // Pop from blocked queue and move the task to the run queue + let wake_up_task = current_blocked_queue.pop(); + let pid: u16; + if wake_up_task.is_none() { + log!( + LogLevel::Error, + "Error getting the wake up task from blocked queue, blocked queue or need_reschedule flag can be corrupted." + ); + // Trigger a context switch on current task to avoid to fail-fast + // TODO: + return; + } else { + // Allow unwrap, we check the value before + pid = wake_up_task.unwrap().id as u16; + } + // Consider the `pid` as init, if wake_up_task.is_none(), we switch on the current task, so + // we cannot reach this point unless wake_up_task is some and `pid` is set. + let mut task = task_list_get_task_by_pid(pid).expect("Failed to get the task by it's pid."); + let priority: u8 = task_priority(&task); + task_awake_block_control(task); + task.state = TaskState::Ready; + current_run_queue[priority as usize].push(pid); + clear_reschedule(); + } // Current running task let mut current_task = unsafe { *TASK_HANDLER }; + if current_task.state == TaskState::Blocked { + let pid = task_pid(¤t_task); + let awake_tick = task_awake_tick(¤t_task).expect("Failed to get the task awake_tick"); + // Push the current task to the blocked queue + current_blocked_queue.push(pid as usize, awake_tick) + } if current_task.state != TaskState::Blocked { current_task.state = TaskState::Ready; let pid = task_pid(¤t_task); @@ -71,14 +111,7 @@ pub fn scheduler() { // Push current task to the priority buffer current_run_queue[priority].push(pid) } - let resched = read_need_reschedule(); - if resched { - log!( - LogLevel::Debug, - "Reschedule needed, clearing the need reschedule bit." - ); - clear_reschedule(); - } + // Update and load next task #[allow(static_mut_refs)] let is_no_task = current_run_queue_bitmap.is_bitmap_zero(); diff --git a/src/task/mod.rs b/src/task/mod.rs index e939a8e..33705cf 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -180,6 +180,17 @@ pub fn task_priority(task: &Task) -> u8 { task.priority } +pub fn task_awake_tick(task: &Task) -> Option { + match task.block_control { + TaskBlockControl::AwakeTick(tick) => Some(tick), + TaskBlockControl::None => None, + } +} + +pub fn task_awake_block_control(task: &mut Task) { + task.block_control = TaskBlockControl::None; +} + /// Create the idle task pub fn task_idle_task() { let task_name: &str = "Idle task"; diff --git a/src/task/primitives.rs b/src/task/primitives.rs index f22ed10..dc51be7 100644 --- a/src/task/primitives.rs +++ b/src/task/primitives.rs @@ -18,7 +18,7 @@ Tests files: */ use crate::{ - arch::traps::interrupt::enable_and_halt, + arch::{helpers::current_cpu_core, traps::interrupt::enable_and_halt}, ktime::{set_ktime_ms, tick::get_tick}, log, logs::LogLevel, @@ -53,6 +53,8 @@ fn task_set_wake_tick(tick: usize) { } /// Block the current task until the given tick is reach. +/// Update the current task to block it, but the task is still in the run queue, it'll be remove +/// from the run queue and saved in the blocked queue in the scheduler. pub fn task_block_until(tick: usize) { let current_task: *mut Task = unsafe { TASK_HANDLER }; if current_task.is_null() { @@ -77,23 +79,32 @@ pub fn task_block_until(tick: usize) { /// awake, make them ready. Don't handle the queue by itself. /// TODO: Use a better data structure than a RingBuffer for the blocked queue. pub fn task_awake_blocked(tick: usize) { + // Current CPU core + let core: usize = current_cpu_core(); + // Current blocked queue + let current_blocked_queue = &mut unsafe { BLOCKED_QUEUE }[core]; #[allow(static_mut_refs)] - //TODO: use blocked queue size. or something idk - let size = 0; + let size = current_blocked_queue.get_count(); if size == 0 { return; } #[allow(static_mut_refs)] - //TODO: Use blocked queue - let pid = None; - if pid.is_none() { - log!(LogLevel::Error, "Error getting the oldest pid in run queue"); + let mut blocked_task = current_blocked_queue.get_head_node(); + let mut pid: u16; + if blocked_task.is_none() { + log!( + LogLevel::Error, + "Error getting the oldest task in run queue" + ); return; + } else { + // Allow unwrap, we check the value before + pid = blocked_task.unwrap().id as u16; } // 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<>")); + let task = task_list_get_task_by_pid(pid); if task.is_none() { log!( LogLevel::Error, @@ -103,6 +114,8 @@ pub fn task_awake_blocked(tick: usize) { } // Allow expected, we check the value before, if it's some, there's shouldn't be any problem by // unwrapping it. + // TODO: just need to correctly used the blocked queue to avoid getting the task from the + // task_list with pid, and matching on the block_control of the task to awake it. #[allow(clippy::expect_used)] match task .expect("Failed to get the task behind the Option<>. This shouldn't be possible") @@ -113,21 +126,12 @@ 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)] - // RUN_QUEUE.push(pid.expect("Failed to get the pid behind the Option<>")); + // Set the need reschedule flag, the scheduler will check the block queue to + // awake correctly the task. need_reschedule(); }; } 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 => (), From 41698523041d28815522f3a3dcf49fc558adb824 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 16 Feb 2026 09:57:25 +0100 Subject: [PATCH 58/84] docs(scheduler): improve code comment on the need_resched flags in the scheduler --- src/scheduler/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index 1022329..9438939 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -62,6 +62,10 @@ pub fn scheduler() { let current_blocked_queue = &mut unsafe { BLOCKED_QUEUE }[core]; let current_run_queue_bitmap = &mut unsafe { RUN_QUEUE_BITMAP }[core]; // Check the need_reschedule flag + // If a resched has been trigger, pop the head of the blocked queue, update the task and push + // it to the run queue. + // Don't check the awake tick or anything else, we consider that if the need_resched flag is + // true, then the task is available to wake up. let resched = read_need_reschedule(); if !resched { // Get the current task and context switch on it. From e95789e054e3c6a16c5a0b9dc1649fd390b6a842 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 16 Feb 2026 11:06:01 +0100 Subject: [PATCH 59/84] feat(scheduler & task primitives): update the sleep routine to correctly update the current_task, also improve the need_resched flag use in scheduler --- src/main.rs | 30 ++++++++++++++++++++++++++++++ src/scheduler/mod.rs | 40 ++++++++++++++++++++-------------------- src/task/primitives.rs | 13 +++++++------ 3 files changed, 57 insertions(+), 26 deletions(-) diff --git a/src/main.rs b/src/main.rs index ef99bda..45e09c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,8 +59,13 @@ use core::panic::PanicInfo; use logs::LogLevel; use mem::mem_kernel_stack_info; +use scheduler::{RUN_QUEUE, RUN_QUEUE_BITMAP}; #[cfg(feature = "idle_task")] use task::task_idle_task; +use task::{ + TASK_HANDLER, list::task_list_get_task_by_pid, primitives::sleep, task_context_switch, + task_create, +}; #[unsafe(no_mangle)] unsafe extern "C" fn main() -> ! { @@ -75,6 +80,16 @@ unsafe extern "C" fn main() -> ! { log!(LogLevel::Info, "LrnRTOS started!"); #[cfg(feature = "idle_task")] task_idle_task(); + task_create("High priority", hight_priority_task, 10, 0x100); + task_create("Low priority", low_priority_task, 4, 0x100); + // High priority task. + let mut task = task_list_get_task_by_pid(1); + unsafe { TASK_HANDLER = *task.as_mut().unwrap() }; + unsafe { + RUN_QUEUE[0][4].push(2); + RUN_QUEUE_BITMAP[0].set_bit(4); + }; + task_context_switch(task.unwrap()); loop { log!(LogLevel::Debug, "Main loop."); unsafe { @@ -83,6 +98,21 @@ unsafe extern "C" fn main() -> ! { } } +fn hight_priority_task() -> ! { + loop { + print!("This is a high priority task !!!!!!!!!!\n"); + unsafe { sleep(300) }; + } +} + +fn low_priority_task() -> ! { + let mut increment: usize = 0; + loop { + print!("Low priority task: {}\n", increment); + increment += 1; + } +} + #[panic_handler] #[cfg(not(feature = "test"))] fn panic_handler(panic: &PanicInfo) -> ! { diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index 9438939..9d6fcda 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -15,20 +15,12 @@ References: */ use crate::{ - LogLevel, arch::{ helpers::current_cpu_core, - scheduler::{SCHEDULER_CTX, SchedulerCtx, sched_ctx_restore}, - }, - config::{BLOCK_QUEUE_MAX_SIZE, CPU_CORE_NUMBER, RUN_QUEUE_MAX_SIZE, TASK_MAX_PRIORITY}, - log, - misc::{clear_reschedule, read_need_reschedule}, - primitives::{bitmap::Bitmap, indexed_linked_list::IndexedLinkedList, ring_buff::RingBuffer}, - task::{ - TASK_HANDLER, TaskState, - list::{task_list_get_idle_task, task_list_get_task_by_pid, task_list_update_task_by_pid}, - task_awake_block_control, task_awake_tick, task_context_switch, task_pid, task_priority, - }, + scheduler::{sched_ctx_restore, SchedulerCtx, SCHEDULER_CTX}, + }, config::{BLOCK_QUEUE_MAX_SIZE, CPU_CORE_NUMBER, RUN_QUEUE_MAX_SIZE, TASK_MAX_PRIORITY}, kprint, log, misc::{clear_reschedule, read_need_reschedule}, primitives::{bitmap::Bitmap, indexed_linked_list::IndexedLinkedList, ring_buff::RingBuffer}, task::{ + list::{task_list_get_idle_task, task_list_get_task_by_pid, task_list_update_task_by_pid}, task_awake_block_control, task_awake_tick, task_context_switch, task_pid, task_priority, TaskState, TASK_HANDLER + }, LogLevel }; // Reflect the run queue state @@ -63,14 +55,11 @@ pub fn scheduler() { let current_run_queue_bitmap = &mut unsafe { RUN_QUEUE_BITMAP }[core]; // Check the need_reschedule flag // If a resched has been trigger, pop the head of the blocked queue, update the task and push - // it to the run queue. + // it to the run queue. // Don't check the awake tick or anything else, we consider that if the need_resched flag is - // true, then the task is available to wake up. + // true, then the task is available to wake up. let resched = read_need_reschedule(); - if !resched { - // Get the current task and context switch on it. - return; - } else { + if resched { log!( LogLevel::Debug, "Reschedule needed, updating queues, clearing the need reschedule bit." @@ -96,16 +85,25 @@ pub fn scheduler() { let priority: u8 = task_priority(&task); task_awake_block_control(task); task.state = TaskState::Ready; + task_list_update_task_by_pid(pid, *task); current_run_queue[priority as usize].push(pid); + current_run_queue_bitmap.set_bit(priority as usize); clear_reschedule(); } // Current running task let mut current_task = unsafe { *TASK_HANDLER }; if current_task.state == TaskState::Blocked { let pid = task_pid(¤t_task); + let priority = task_priority(¤t_task); let awake_tick = task_awake_tick(¤t_task).expect("Failed to get the task awake_tick"); // Push the current task to the blocked queue - current_blocked_queue.push(pid as usize, awake_tick) + current_blocked_queue.push(pid as usize, awake_tick); + // Check the run queue from the current_task priority. + // If the run queue is empty, clean the run queue bitmap for this priority bit. + let is_run_queue_empty = current_run_queue[priority as usize].size(); + if is_run_queue_empty == 0 { + current_run_queue_bitmap.clear_bit(priority as usize); + } } if current_task.state != TaskState::Blocked { current_task.state = TaskState::Ready; @@ -113,7 +111,9 @@ pub fn scheduler() { let priority: usize = task_priority(¤t_task).into(); task_list_update_task_by_pid(pid, current_task); // Push current task to the priority buffer - current_run_queue[priority].push(pid) + current_run_queue[priority as usize].push(pid); + // Update the bitmap priority bit. + current_run_queue_bitmap.set_bit(priority as usize); } // Update and load next task diff --git a/src/task/primitives.rs b/src/task/primitives.rs index dc51be7..b92f90d 100644 --- a/src/task/primitives.rs +++ b/src/task/primitives.rs @@ -19,6 +19,7 @@ Tests files: use crate::{ arch::{helpers::current_cpu_core, traps::interrupt::enable_and_halt}, + kprint_fmt, ktime::{set_ktime_ms, tick::get_tick}, log, logs::LogLevel, @@ -64,15 +65,15 @@ pub fn task_block_until(tick: usize) { ); // 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); + unsafe { + // Deref and cast current_task to &mut to update the Task behind the ptr. + let task: &mut Task = &mut *current_task; + task.state = TaskState::Blocked; + task.block_control = TaskBlockControl::AwakeTick(tick); + } } /// Check all the blocked queue to find the task to awake. Just update the task that need to be From 90c0bb63cbe3c0ea00cc094888a9b5f55b9cac1e Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 16 Feb 2026 14:51:59 +0100 Subject: [PATCH 60/84] fix: the scheduler blocked queue not working with task primitives Preemptive scheduler working, there's an exception after few cycles, don't know why --- src/primitives/bitmap.rs | 5 ++--- src/scheduler/mod.rs | 25 ++++++++++++++++++------- src/task/primitives.rs | 4 ++-- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/primitives/bitmap.rs b/src/primitives/bitmap.rs index a525dff..48e3829 100644 --- a/src/primitives/bitmap.rs +++ b/src/primitives/bitmap.rs @@ -1,6 +1,6 @@ #[derive(Copy, Clone)] pub struct Bitmap { - map: u32, + pub map: u32, } impl Bitmap { @@ -16,8 +16,7 @@ impl Bitmap { /// Clear the given bit. Set it to 0. pub fn clear_bit(&mut self, bit: usize) { - let mask = 0 << bit; - self.map &= mask; + self.map &= !(1 << bit); } /// Iterate over the bitmap and return the heavier bit. diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index 9d6fcda..866a467 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -15,12 +15,20 @@ References: */ use crate::{ + LogLevel, arch::{ helpers::current_cpu_core, - scheduler::{sched_ctx_restore, SchedulerCtx, SCHEDULER_CTX}, - }, config::{BLOCK_QUEUE_MAX_SIZE, CPU_CORE_NUMBER, RUN_QUEUE_MAX_SIZE, TASK_MAX_PRIORITY}, kprint, log, misc::{clear_reschedule, read_need_reschedule}, primitives::{bitmap::Bitmap, indexed_linked_list::IndexedLinkedList, ring_buff::RingBuffer}, task::{ - list::{task_list_get_idle_task, task_list_get_task_by_pid, task_list_update_task_by_pid}, task_awake_block_control, task_awake_tick, task_context_switch, task_pid, task_priority, TaskState, TASK_HANDLER - }, LogLevel + scheduler::{SCHEDULER_CTX, SchedulerCtx, sched_ctx_restore}, + }, + config::{BLOCK_QUEUE_MAX_SIZE, CPU_CORE_NUMBER, RUN_QUEUE_MAX_SIZE, TASK_MAX_PRIORITY}, + kprint, kprint_fmt, log, + misc::{clear_reschedule, read_need_reschedule}, + primitives::{bitmap::Bitmap, indexed_linked_list::IndexedLinkedList, ring_buff::RingBuffer}, + task::{ + TASK_HANDLER, TaskState, + list::{task_list_get_idle_task, task_list_get_task_by_pid, task_list_update_task_by_pid}, + task_awake_block_control, task_awake_tick, task_context_switch, task_pid, task_priority, + }, }; // Reflect the run queue state @@ -49,10 +57,12 @@ pub static mut BLOCKED_QUEUE: [IndexedLinkedList; CPU_CORE pub fn scheduler() { let core: usize = current_cpu_core(); #[allow(static_mut_refs)] - let current_run_queue = &mut unsafe { RUN_QUEUE }[core]; + let current_run_queue = unsafe { &mut RUN_QUEUE[core] }; #[allow(static_mut_refs)] - let current_blocked_queue = &mut unsafe { BLOCKED_QUEUE }[core]; - let current_run_queue_bitmap = &mut unsafe { RUN_QUEUE_BITMAP }[core]; + let current_blocked_queue = unsafe { &mut BLOCKED_QUEUE[core] }; + #[allow(static_mut_refs)] + let current_run_queue_bitmap = unsafe { &mut RUN_QUEUE_BITMAP[core] }; + // Check the need_reschedule flag // If a resched has been trigger, pop the head of the blocked queue, update the task and push // it to the run queue. @@ -105,6 +115,7 @@ pub fn scheduler() { current_run_queue_bitmap.clear_bit(priority as usize); } } + if current_task.state != TaskState::Blocked { current_task.state = TaskState::Ready; let pid = task_pid(¤t_task); diff --git a/src/task/primitives.rs b/src/task/primitives.rs index b92f90d..18c45e3 100644 --- a/src/task/primitives.rs +++ b/src/task/primitives.rs @@ -19,7 +19,7 @@ Tests files: use crate::{ arch::{helpers::current_cpu_core, traps::interrupt::enable_and_halt}, - kprint_fmt, + kprint, kprint_fmt, ktime::{set_ktime_ms, tick::get_tick}, log, logs::LogLevel, @@ -83,7 +83,7 @@ pub fn task_awake_blocked(tick: usize) { // Current CPU core let core: usize = current_cpu_core(); // Current blocked queue - let current_blocked_queue = &mut unsafe { BLOCKED_QUEUE }[core]; + let current_blocked_queue = unsafe { BLOCKED_QUEUE }[core]; #[allow(static_mut_refs)] let size = current_blocked_queue.get_count(); if size == 0 { From 077e77a5efdb718761dd73400fc4cc5b6d0c72c4 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 16 Feb 2026 16:49:20 +0100 Subject: [PATCH 61/84] feat(primitives): update the indexed linked list to correctly pop --- src/primitives/indexed_linked_list.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/primitives/indexed_linked_list.rs b/src/primitives/indexed_linked_list.rs index 2674ca4..ffc88ba 100644 --- a/src/primitives/indexed_linked_list.rs +++ b/src/primitives/indexed_linked_list.rs @@ -70,7 +70,8 @@ impl IndexedLinkedList { if node.id == id { log!( LogLevel::Warn, - "The delta-list is full, abort push. Consider increasing the blocked queue size." + "The indexed-linked-list has already the id: {}, abort push.", + id ); return; } else { @@ -160,9 +161,9 @@ impl IndexedLinkedList { self.count += 1; } - /// Remove the node at the head and return a mutable reference to it. + /// Remove the node at the head and return the node. /// Update the linked list head to point to the next node. - pub fn pop(&mut self) -> Option<&mut IndexedLinkedListNode> { + pub fn pop(&mut self) -> Option { let head = self.head; let head_next_node = { let head_node = self.get_node(head).expect("Failed to get the node."); @@ -174,7 +175,9 @@ impl IndexedLinkedList { self.head = head_next_node .expect("Failed to get the usize behind the Option<> in node.next_node"); } - self.get_node(head) + self.count -= 1; + // Get the head node + self.take_node(head) } pub fn get_head_node(&self) -> Option<&IndexedLinkedListNode> { @@ -190,6 +193,16 @@ impl IndexedLinkedList { } } + /// Take the node from the given index, replace it with None in the list. + fn take_node(&mut self, idx: usize) -> Option { + let mut node = self.list[idx]; + if node.is_some() { + return self.list[idx].take(); + } else { + return None; + } + } + pub fn size(&self) -> usize { let mut output: usize = 0; for i in 0..self.list.len() { From 7cfe8a1e34c98cb6dc78e99a564b33459bd0249d Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 17 Feb 2026 09:47:11 +0100 Subject: [PATCH 62/84] fix(arch riscv32): no more crash when trigger a reschedule, the runtime keep running Still the low priority task restarting from 0. --- src/arch/riscv32/asm/trap_entry.S | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/arch/riscv32/asm/trap_entry.S b/src/arch/riscv32/asm/trap_entry.S index 529807d..e4d6be3 100644 --- a/src/arch/riscv32/asm/trap_entry.S +++ b/src/arch/riscv32/asm/trap_entry.S @@ -42,6 +42,11 @@ trap_entry: mv a5, t6 # Call trap_handler rust function call trap_handler + + # Check if a re-schedule is needed or not. + call read_need_reschedule + # If a0 != 0, goto 1f, else mret + bnez a0, 1f # Load all register back to previous state csrr t6, mscratch # Restore all GP registers @@ -50,13 +55,17 @@ trap_entry: load_gp %i .set i, i+1 .endr - # Check if a re-schedule is needed or not. - call read_need_reschedule - # If a0 != 0, goto 1f, else mret - bnez a0, 1f mret 1: call scheduler + # Load all register back to previous state + csrr t6, mscratch + # Restore all GP registers + .set i, 1 + .rept 31 + load_gp %i + .set i, i+1 + .endr mret # Function used when the trap strack = 0, just infinite loop for debuging purpose From 42f2f80e414c02a8316009d1ea8c0b1f1e554bbf Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 17 Feb 2026 10:59:12 +0100 Subject: [PATCH 63/84] feat(arch riscv32): remove the save of mstatus in save_context, and update trap entry to save the task context and restore it if there's no need reschedule Remove the mstatus save, risky in trap handling if mstatus change --- src/arch/riscv32/asm/save_context.S | 3 -- src/arch/riscv32/asm/trap_entry.S | 48 ++++++++++------------------- 2 files changed, 16 insertions(+), 35 deletions(-) diff --git a/src/arch/riscv32/asm/save_context.S b/src/arch/riscv32/asm/save_context.S index de3d8e5..97f0b54 100644 --- a/src/arch/riscv32/asm/save_context.S +++ b/src/arch/riscv32/asm/save_context.S @@ -9,9 +9,6 @@ save_context: sw a1, OFFSET_RA(t6) # Store word from t0(sp) in context structure sw a2, OFFSET_SP(t6) - # Save mstatus - csrr t0, mstatus - sw t0, OFFSET_MSTATUS(t6) # Save current task context using GNU macro # GNU macros from `src/arch/riscv32/asm/gnu_macro.S` .set i, 1 diff --git a/src/arch/riscv32/asm/trap_entry.S b/src/arch/riscv32/asm/trap_entry.S index e4d6be3..955e862 100644 --- a/src/arch/riscv32/asm/trap_entry.S +++ b/src/arch/riscv32/asm/trap_entry.S @@ -11,22 +11,19 @@ .global trap_entry .type trap_entry, @function trap_entry: + # Save task context + # 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 # Read mscratch into t6 csrr t6, mscratch - # Save all GP registers using GNU macro - # Just a loop - .set i, 1 - .rept 31 - save_gp %i, t6 - .set i, i+1 - .endr - # Read CSR and store word in correct offset of trap frame structure - csrr t0, satp - sw t0, OFFSET_SATP(t6) # store satp - csrr t0, mhartid - sw t0, OFFSET_HARTID(t6) # store hartid (optional, redundant) - sw sp, (OFFSET_TRAP_STACK - 4)(t6) # store original sp just before trap_stack slot - # Load content of trap stack offset into t1 lw t1, OFFSET_TRAP_STACK(t6) # t1 = trap_stack pointer # Branch instruction to check if t1 = 0 beqz t1, trap_no_trapstack @@ -47,26 +44,13 @@ trap_entry: call read_need_reschedule # If a0 != 0, goto 1f, else mret bnez a0, 1f - # Load all register back to previous state - csrr t6, mscratch - # Restore all GP registers - .set i, 1 - .rept 31 - load_gp %i - .set i, i+1 - .endr - mret + # If there's no need to a reschedule, restore the task context + la t0, TASK_HANDLER # Address in RAM + lw t1, 0(t0) # Get the value behind the ref + mv a0, t1 + call restore_context 1: call scheduler - # Load all register back to previous state - csrr t6, mscratch - # Restore all GP registers - .set i, 1 - .rept 31 - load_gp %i - .set i, i+1 - .endr - mret # Function used when the trap strack = 0, just infinite loop for debuging purpose trap_no_trapstack: From f3c74908ba8bc5c67aea430a897c5a7cb32fb9a8 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 17 Feb 2026 15:31:28 +0100 Subject: [PATCH 64/84] fix(arch riscv32): trap entry to use mepc to return to task execution, fix task context to use correct mstatus value Task context mstatus has been update to use 6152 instead of 8 to enable M-mode on task,for now at least --- src/arch/riscv32/asm/restore_context.S | 26 +++++++++++++++++++++++++- src/arch/riscv32/asm/save_context.S | 19 +++++++++++++++++++ src/arch/riscv32/asm/trap_entry.S | 10 +++++----- src/arch/riscv32/task/task_context.rs | 4 ++-- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/arch/riscv32/asm/restore_context.S b/src/arch/riscv32/asm/restore_context.S index 61f7965..8c7d540 100644 --- a/src/arch/riscv32/asm/restore_context.S +++ b/src/arch/riscv32/asm/restore_context.S @@ -12,7 +12,7 @@ restore_context: # Restore sp from current task context structure lw t0, OFFSET_SP(t6) mv sp, t0 - ## Update mstatus + # Update mstatus lw t0, OFFSET_MSTATUS(t6) csrw mstatus, t0 # Restore current task context using GNU macro @@ -23,3 +23,27 @@ restore_context: .set i, i+1 .endr ret + +.global trap_restore_context +.type trap_restore_context, @function +trap_restore_context: + # Move current task context struct save in caller in a0 reg to t6 + mv t6, a0 + # Update sp + # Restore sp from current task context structure + lw t0, OFFSET_SP(t6) + mv sp, t0 + # Update mstatus + lw t0, OFFSET_MSTATUS(t6) + csrw mstatus, t0 + # Update mepc + lw t0, OFFSET_PC(t6) + csrw mepc, t0 + # Restore current task context using GNU macro + # GNU macros from `src/arch/riscv32/asm/gnu_macro.S` + .set i, 1 + .rept 31 + load_gp_context %i + .set i, i+1 + .endr + mret diff --git a/src/arch/riscv32/asm/save_context.S b/src/arch/riscv32/asm/save_context.S index 97f0b54..e5bd35a 100644 --- a/src/arch/riscv32/asm/save_context.S +++ b/src/arch/riscv32/asm/save_context.S @@ -9,6 +9,25 @@ save_context: sw a1, OFFSET_RA(t6) # Store word from t0(sp) in context structure sw a2, OFFSET_SP(t6) + sw a3, OFFSET_PC(t6) + # Save current task context using GNU macro + # GNU macros from `src/arch/riscv32/asm/gnu_macro.S` + .set i, 1 + .rept 31 + save_gp_context %i + .set i, i+1 + .endr + ret + +.global trap_save_context +.type trap_save_context, @function +trap_save_context: + # Move current task context struct save in caller in a0 reg to t6 + mv t6, a0 + # Store ra in task context structure + sw a1, OFFSET_PC(t6) + # Store word from t0(sp) in context structure + sw a2, OFFSET_SP(t6) # Save current task context using GNU macro # GNU macros from `src/arch/riscv32/asm/gnu_macro.S` .set i, 1 diff --git a/src/arch/riscv32/asm/trap_entry.S b/src/arch/riscv32/asm/trap_entry.S index 955e862..9d574a6 100644 --- a/src/arch/riscv32/asm/trap_entry.S +++ b/src/arch/riscv32/asm/trap_entry.S @@ -16,13 +16,13 @@ trap_entry: 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 pc + csrr a1, mepc # Save current sp mv a2, sp # Call the save context function - call save_context - # Read mscratch into t6 + call trap_save_context + # Read mscratch into t7 csrr t6, mscratch lw t1, OFFSET_TRAP_STACK(t6) # t1 = trap_stack pointer # Branch instruction to check if t1 = 0 @@ -48,7 +48,7 @@ trap_entry: la t0, TASK_HANDLER # Address in RAM lw t1, 0(t0) # Get the value behind the ref mv a0, t1 - call restore_context + call trap_restore_context 1: call scheduler diff --git a/src/arch/riscv32/task/task_context.rs b/src/arch/riscv32/task/task_context.rs index fa7f034..af41ed4 100644 --- a/src/arch/riscv32/task/task_context.rs +++ b/src/arch/riscv32/task/task_context.rs @@ -18,7 +18,7 @@ Tests files: use super::{restore_context, save_context}; #[repr(C)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub struct TaskContext { pub gpr: [u32; 32], // Offset 0 pub address_space: [u32; 2], // Offset 128 (first index 128; second index 132) @@ -39,7 +39,7 @@ impl TaskContext { sp: size[0] as u32, ra: func as usize as u32, // Set mstatus to 8 by default to enable mie - mstatus: 8, + mstatus: 6152, flags: [0u8; 3], instruction_register: 0, } From db707bcb27d5163a18869b2c3ab93ae516e79d61 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 17 Feb 2026 16:04:34 +0100 Subject: [PATCH 65/84] feat(arch riscv32): improve mstatus handling on save and restore context --- src/arch/riscv32/asm/restore_context.S | 12 ++++++------ src/arch/riscv32/asm/save_context.S | 6 ++++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/arch/riscv32/asm/restore_context.S b/src/arch/riscv32/asm/restore_context.S index 8c7d540..2e89eba 100644 --- a/src/arch/riscv32/asm/restore_context.S +++ b/src/arch/riscv32/asm/restore_context.S @@ -12,9 +12,6 @@ restore_context: # Restore sp from current task context structure lw t0, OFFSET_SP(t6) mv sp, t0 - # Update mstatus - lw t0, OFFSET_MSTATUS(t6) - csrw mstatus, t0 # Restore current task context using GNU macro # GNU macros from `src/arch/riscv32/asm/gnu_macro.S` .set i, 1 @@ -22,6 +19,9 @@ restore_context: load_gp_context %i .set i, i+1 .endr + # Update mstatus + li t0, ((3 << 11) | (1 << 3)) + csrrs x0, mstatus, t0 ret .global trap_restore_context @@ -33,9 +33,6 @@ trap_restore_context: # Restore sp from current task context structure lw t0, OFFSET_SP(t6) mv sp, t0 - # Update mstatus - lw t0, OFFSET_MSTATUS(t6) - csrw mstatus, t0 # Update mepc lw t0, OFFSET_PC(t6) csrw mepc, t0 @@ -46,4 +43,7 @@ trap_restore_context: load_gp_context %i .set i, i+1 .endr + # Update mstatus + li t0, ((3 << 11) | (1 << 7)) + csrrs x0, mstatus, t0 mret diff --git a/src/arch/riscv32/asm/save_context.S b/src/arch/riscv32/asm/save_context.S index e5bd35a..9829e8e 100644 --- a/src/arch/riscv32/asm/save_context.S +++ b/src/arch/riscv32/asm/save_context.S @@ -3,6 +3,9 @@ .global save_context .type save_context, @function save_context: + # Clear mstatus.MPP and mstatus.mie to avoid interruption while saving and switching context + li t0, ((3 << 11) | (1 << 3)) + csrrc x0, mstatus, t0 # Move current task context struct save in caller in a0 reg to t6 mv t6, a0 # Store ra in task context structure @@ -22,6 +25,9 @@ save_context: .global trap_save_context .type trap_save_context, @function trap_save_context: + # Clear mstatus.MPP and mstatus.mie to avoid interruption while saving and switching context + li t0, ((3 << 11) | (1 << 3)) + csrrc x0, mstatus, t0 # Move current task context struct save in caller in a0 reg to t6 mv t6, a0 # Store ra in task context structure From 7825b877ae2ac68760fc4ecf9fb1c62a20f0cef1 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 17 Feb 2026 16:20:32 +0100 Subject: [PATCH 66/84] feat: preemptif scheduler work Things are smelly, like if two task with different priority print, it can cause race condition and corrupt uart driver leading to a complete kernel crash(mcause = 7), but if the print or logging system is protected with a mutex, or while printing, we deactivate interrupt, there's no problem. --- src/main.rs | 11 +++++------ src/scheduler/mod.rs | 2 -- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 45e09c9..27b2c8e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,6 +54,7 @@ pub mod scheduler; pub mod tests; // Use from modules +use arch::traps::misc::{read_mie, read_mstatus}; #[cfg(not(feature = "test"))] use core::panic::PanicInfo; use logs::LogLevel; @@ -80,8 +81,8 @@ unsafe extern "C" fn main() -> ! { log!(LogLevel::Info, "LrnRTOS started!"); #[cfg(feature = "idle_task")] task_idle_task(); - task_create("High priority", hight_priority_task, 10, 0x100); - task_create("Low priority", low_priority_task, 4, 0x100); + task_create("High priority", hight_priority_task, 10, 0x1000); + task_create("Low priority", low_priority_task, 4, 0x1000); // High priority task. let mut task = task_list_get_task_by_pid(1); unsafe { TASK_HANDLER = *task.as_mut().unwrap() }; @@ -101,15 +102,13 @@ unsafe extern "C" fn main() -> ! { fn hight_priority_task() -> ! { loop { print!("This is a high priority task !!!!!!!!!!\n"); - unsafe { sleep(300) }; + unsafe { sleep(10) }; } } fn low_priority_task() -> ! { - let mut increment: usize = 0; loop { - print!("Low priority task: {}\n", increment); - increment += 1; + // print!("Low priority task\n"); } } diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index 866a467..8343eea 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -62,7 +62,6 @@ pub fn scheduler() { let current_blocked_queue = unsafe { &mut BLOCKED_QUEUE[core] }; #[allow(static_mut_refs)] let current_run_queue_bitmap = unsafe { &mut RUN_QUEUE_BITMAP[core] }; - // Check the need_reschedule flag // If a resched has been trigger, pop the head of the blocked queue, update the task and push // it to the run queue. @@ -126,7 +125,6 @@ pub fn scheduler() { // Update the bitmap priority bit. current_run_queue_bitmap.set_bit(priority as usize); } - // Update and load next task #[allow(static_mut_refs)] let is_no_task = current_run_queue_bitmap.is_bitmap_zero(); From 7bc314754c4687e718db00c158eb3dc670e76444 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 17 Feb 2026 16:23:10 +0100 Subject: [PATCH 67/84] feat(arch riscv32): remove the hardcoded mstatus value, prefer handling mstatus from context routine --- src/arch/riscv32/asm/context_offset.S | 5 ++--- src/arch/riscv32/task/task_context.rs | 6 ++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/arch/riscv32/asm/context_offset.S b/src/arch/riscv32/asm/context_offset.S index fab291f..07c4a6b 100644 --- a/src/arch/riscv32/asm/context_offset.S +++ b/src/arch/riscv32/asm/context_offset.S @@ -11,7 +11,6 @@ .set OFFSET_SP, 140 # Return address .set OFFSET_RA, 144 -.set OFFSET_MSTATUS, 148 # Optionnal flags, will be use later maybe -.set OFFSET_FLAGS, 152 -.set OFFSET_INSTRUCTION_REG, 155 +.set OFFSET_FLAGS, 148 +.set OFFSET_INSTRUCTION_REG, 151 diff --git a/src/arch/riscv32/task/task_context.rs b/src/arch/riscv32/task/task_context.rs index af41ed4..9f4f9fa 100644 --- a/src/arch/riscv32/task/task_context.rs +++ b/src/arch/riscv32/task/task_context.rs @@ -25,9 +25,8 @@ pub struct TaskContext { pub pc: u32, // Offset 136 pub sp: u32, // Offset 140 pub ra: u32, // Offset 144 - pub mstatus: u32, // Offset 148 - pub flags: [u8; 3], // Offset 152 (first index 152; second index 153, third index 154) - pub instruction_register: u8, // Offset 155 + pub flags: [u8; 3], // Offset 148 (first index 148; second index 149, third index 150) + pub instruction_register: u8, // Offset 151 } impl TaskContext { @@ -39,7 +38,6 @@ impl TaskContext { sp: size[0] as u32, ra: func as usize as u32, // Set mstatus to 8 by default to enable mie - mstatus: 6152, flags: [0u8; 3], instruction_register: 0, } From 8b15c3712c0e455e61dd9758946456530f549342 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 17 Feb 2026 18:28:05 +0100 Subject: [PATCH 68/84] docs(kernel): add basic scheduler doc --- Documentation/kernel/scheduler.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 Documentation/kernel/scheduler.md diff --git a/Documentation/kernel/scheduler.md b/Documentation/kernel/scheduler.md new file mode 100644 index 0000000..4cc24ff --- /dev/null +++ b/Documentation/kernel/scheduler.md @@ -0,0 +1,28 @@ +# Kernel scheduler + +## Description + +The kernel scheduler is in charge of managing which task to run, updating the run queue and the blocked queue and updating the task state. + +### Queues + +There's 2 queues used in the scheduler. The `run queue` and the `blocked queue`. Each queue is specific for a `CPU core`. The `CPU core 1` has a different `run queue` than the `CPU core 2`. + +### Run queue + +The `run queue` contains all the task ready to be run. In the `ready` task state. + +The `run queue` work using a `FIFO` queue, there's a queue for each `priority`, up to `32 queues`. +To find which queue to use to execute a task, instead of iterating over all queues, we use a `bitmap`. +There's only `32 priorities`, so we use a `u32 bitmap`, each bit representing a `run queue`, if the bit is set, there's at least 1 task to run. +Else, the queue is empty. + +### Blocked queue + +The `blocked queue` contains all the task currently blocked. With different block reasons. +But currently the `blocked queue` contains uniquely the task blocked using the `sleep` task primitive. + +The `blocked queue` work using an `indexed linked list`, the list is sorted from the shortest awake tick to the largest awake tick. +The list is manage using `head` and `tail`, a bit like a `ring buffer` data structure. +So when we need to check the next task to awake, we just check the `head` of the `blocked queue`. +If it can be awake, it will update the `need_resched` flag, From ea5eafb232e03fdef9cc1d11f5481866388a928b Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 19 Feb 2026 09:10:06 +0100 Subject: [PATCH 69/84] docs(kernel): update the scheduler doc, add invariants and toc --- Documentation/kernel/scheduler.md | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/Documentation/kernel/scheduler.md b/Documentation/kernel/scheduler.md index 4cc24ff..408faf1 100644 --- a/Documentation/kernel/scheduler.md +++ b/Documentation/kernel/scheduler.md @@ -1,10 +1,20 @@ # Kernel scheduler + +- [Kernel scheduler](#kernel-scheduler) + - [Description](#description) + - [Queues](#queues) + - [Run queue](#run-queue) + - [Blocked queue](#blocked-queue) + - [Preemption](#preemption) + - [Invariants](#invariants) + + ## Description The kernel scheduler is in charge of managing which task to run, updating the run queue and the blocked queue and updating the task state. -### Queues +## Queues There's 2 queues used in the scheduler. The `run queue` and the `blocked queue`. Each queue is specific for a `CPU core`. The `CPU core 1` has a different `run queue` than the `CPU core 2`. @@ -12,9 +22,9 @@ There's 2 queues used in the scheduler. The `run queue` and the `blocked queue`. The `run queue` contains all the task ready to be run. In the `ready` task state. -The `run queue` work using a `FIFO` queue, there's a queue for each `priority`, up to `32 queues`. +The `run queue` work using a `FIFO` queue, there's a queue for each `priority`, up to `32 queues`. To find which queue to use to execute a task, instead of iterating over all queues, we use a `bitmap`. -There's only `32 priorities`, so we use a `u32 bitmap`, each bit representing a `run queue`, if the bit is set, there's at least 1 task to run. +There's only `32 priorities`, so we use a `u32 bitmap`, each bit representing a `run queue`, if the bit is set, there's at least 1 task to run. Else, the queue is empty. ### Blocked queue @@ -25,4 +35,15 @@ But currently the `blocked queue` contains uniquely the task blocked using the ` The `blocked queue` work using an `indexed linked list`, the list is sorted from the shortest awake tick to the largest awake tick. The list is manage using `head` and `tail`, a bit like a `ring buffer` data structure. So when we need to check the next task to awake, we just check the `head` of the `blocked queue`. -If it can be awake, it will update the `need_resched` flag, +If it can be awake, it will update the `need_resched` flag, then it'll trigger a reschedule, the scheduler will be able to awake the task, and move it from the `blocked queue` to the `run queue`. + +## Preemption + +The scheduler is preemptive, meaning that if a task as a higher priority than the current task, it will run this higher priority task. +The current task having a lowest priority, will be saved, and re-execute when there's no higher priority task to run. + +## Invariants + +- The scheduler need at least one task in the `run queue`, if the `run queue` is empty, it will try to run the idle task. Make sure that there's always at least one task in the `run queue`, or enable the `idle task` feature. +- The scheduler assume that the `CpusState` is initialized to access the `CPU core scheduler state`. +- The scheduler can be called from the `trap epilogue`, if so, a `trap frame`, should be available and accessible for the scheduler to run on. From 537a3f5793c86b813011fdf1bba99375928eb8dc Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 19 Feb 2026 09:29:35 +0100 Subject: [PATCH 70/84] docs(kernel): add the scheduling model section --- Documentation/kernel/scheduler.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/kernel/scheduler.md b/Documentation/kernel/scheduler.md index 408faf1..e78dd86 100644 --- a/Documentation/kernel/scheduler.md +++ b/Documentation/kernel/scheduler.md @@ -7,6 +7,7 @@ - [Run queue](#run-queue) - [Blocked queue](#blocked-queue) - [Preemption](#preemption) + - [Scheduling model](#scheduling-model) - [Invariants](#invariants) @@ -42,6 +43,12 @@ If it can be awake, it will update the `need_resched` flag, then it'll trigger a The scheduler is preemptive, meaning that if a task as a higher priority than the current task, it will run this higher priority task. The current task having a lowest priority, will be saved, and re-execute when there's no higher priority task to run. +## Scheduling model + +The current scheduling model is a `cooperative priority based`. Meaning that if there's `2 task` with the same `priority`, if they don't call `yield`, one task will `always run`. +Making the other task `starving`. +So if you want to switch from a `task` to another one, you need to use `cooperative` functions, like `yield` or `sleep`. + ## Invariants - The scheduler need at least one task in the `run queue`, if the `run queue` is empty, it will try to run the idle task. Make sure that there's always at least one task in the `run queue`, or enable the `idle task` feature. From 10d2bc1eaeb5064839415a8dbcdf79ea36712a45 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 19 Feb 2026 09:35:28 +0100 Subject: [PATCH 71/84] docs(kernel): update the scheduler invariants with temp invariant No runtime starting for now, execute the task manually --- Documentation/kernel/scheduler.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/kernel/scheduler.md b/Documentation/kernel/scheduler.md index e78dd86..4569adc 100644 --- a/Documentation/kernel/scheduler.md +++ b/Documentation/kernel/scheduler.md @@ -54,3 +54,4 @@ So if you want to switch from a `task` to another one, you need to use `cooperat - The scheduler need at least one task in the `run queue`, if the `run queue` is empty, it will try to run the idle task. Make sure that there's always at least one task in the `run queue`, or enable the `idle task` feature. - The scheduler assume that the `CpusState` is initialized to access the `CPU core scheduler state`. - The scheduler can be called from the `trap epilogue`, if so, a `trap frame`, should be available and accessible for the scheduler to run on. +- For now the scheduler assume that there's always a `current running task`, if not, and the scheduler is triggered, this could lead to UB. From afbaeb935c94e6fe32340ef1520116457f04512e Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 19 Feb 2026 09:36:59 +0100 Subject: [PATCH 72/84] feat(tests): add the indexed linked list test suite --- src/tests/suites.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tests/suites.rs b/src/tests/suites.rs index 58c5312..79989d7 100644 --- a/src/tests/suites.rs +++ b/src/tests/suites.rs @@ -14,6 +14,7 @@ use super::{ ktime::ktime_test_suite, mem::memory_test_suite, platform::platform_test_suite, + primitives::indexed_linked_list::indexed_linked_list_primitive_test_suite, primitives::ring_buff::ring_buff_primitive_test_suite, task::{list::task_list_test_suite, primitives::task_primitives_test_suite, task_test_suite}, }; @@ -24,6 +25,7 @@ pub fn test_suites() { platform_test_suite(); serial_subsystem_test_suite(); ring_buff_primitive_test_suite(); + indexed_linked_list_primitive_test_suite(); timer_subsystem_test_suite(); cpu_intc_subsystem_test_suite(); ktime_test_suite(); From 05db04743c8b23ae49cc4eeab65c35f08580354e Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 19 Feb 2026 09:42:41 +0100 Subject: [PATCH 73/84] feat(tests): update riscv32 task_context test to use new scheduler queue and remove mstatus in task context --- src/tests/arch/riscv32/task/task_context.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/tests/arch/riscv32/task/task_context.rs b/src/tests/arch/riscv32/task/task_context.rs index ff7c891..7388325 100644 --- a/src/tests/arch/riscv32/task/task_context.rs +++ b/src/tests/arch/riscv32/task/task_context.rs @@ -3,6 +3,7 @@ use crate::scheduler::RUN_QUEUE; use core::{mem, ptr}; use crate::{ + RUN_QUEUE_BITMAP, arch::{scheduler::init_sched_ctx, task::task_context::TaskContext, traps::interrupt::halt}, scheduler::scheduler, task::{ @@ -51,12 +52,6 @@ pub fn test_task_context_init() -> u8 { "Task context has been initialized with wrong SP, expect sp to be set to the hi address of the task address space" ); } - // Check mstatus - if task_context.mstatus != 8 { - panic!( - "Task context has been initialized with wrong mstatus, expect mstatus to be set to 8 to only enable mstatus.mie" - ); - } 0 } @@ -81,10 +76,6 @@ pub fn test_task_context_offset() -> u8 { if ra_off != 144 { panic!("Task context ra offset must be 144, got: {ra_off}"); } - let mstatus_off = mem::offset_of!(TaskContext, mstatus); - if mstatus_off != 148 { - panic!("Task context mstatus offset must be 148, got: {mstatus_off}"); - } let flags_off = mem::offset_of!(TaskContext, flags); if flags_off != 152 { panic!("Task context flags offset must be 144, got: {flags_off}"); @@ -131,11 +122,12 @@ fn test_context_switch_b() -> ! { /// sp ? pub fn test_task_context_switch() -> u8 { // Temporary task creation and retrieving to test context switch. - task_create("A", test_context_switch_a, 0, 0x1000); - task_create("B", test_context_switch_b, 0, 0x1000); + task_create("A", test_context_switch_a, 1, 0x1000); + task_create("B", test_context_switch_b, 1, 0x1000); #[allow(static_mut_refs)] unsafe { - RUN_QUEUE.push(3) + RUN_QUEUE[0][1].push(3); + RUN_QUEUE_BITMAP[0].set_bit(1); }; unsafe { CURRENT_TASK_PID = 2 }; let mut task = task_list_get_task_by_pid(unsafe { CURRENT_TASK_PID }); From a6ed074d45968ebba221507776daeffe0f066769 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 19 Feb 2026 10:03:36 +0100 Subject: [PATCH 74/84] feat(tests): update task_context and task primitives tests --- src/tests/arch/riscv32/task/task_context.rs | 12 ++++-- src/tests/task/primitives.rs | 43 +++++++++++++-------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/tests/arch/riscv32/task/task_context.rs b/src/tests/arch/riscv32/task/task_context.rs index 7388325..2dd4e03 100644 --- a/src/tests/arch/riscv32/task/task_context.rs +++ b/src/tests/arch/riscv32/task/task_context.rs @@ -77,12 +77,12 @@ pub fn test_task_context_offset() -> u8 { panic!("Task context ra offset must be 144, got: {ra_off}"); } let flags_off = mem::offset_of!(TaskContext, flags); - if flags_off != 152 { - panic!("Task context flags offset must be 144, got: {flags_off}"); + if flags_off != 148 { + panic!("Task context flags offset must be 148, got: {flags_off}"); } let instruction_reg_off = mem::offset_of!(TaskContext, instruction_register); - if instruction_reg_off != 155 { - panic!("Task context instruction_register offset must be 147, got: {instruction_reg_off}"); + if instruction_reg_off != 151 { + panic!("Task context instruction_register offset must be 151, got: {instruction_reg_off}"); }; 0 } @@ -122,10 +122,14 @@ fn test_context_switch_b() -> ! { /// sp ? pub fn test_task_context_switch() -> u8 { // Temporary task creation and retrieving to test context switch. + // pid 2 task_create("A", test_context_switch_a, 1, 0x1000); + // pid 3 task_create("B", test_context_switch_b, 1, 0x1000); #[allow(static_mut_refs)] unsafe { + // Access the queue and bitmap from CPU core 0 + // run queue priority 1 RUN_QUEUE[0][1].push(3); RUN_QUEUE_BITMAP[0].set_bit(1); }; diff --git a/src/tests/task/primitives.rs b/src/tests/task/primitives.rs index c96605c..02c764d 100644 --- a/src/tests/task/primitives.rs +++ b/src/tests/task/primitives.rs @@ -1,9 +1,13 @@ use crate::{ - arch::traps::{ - disable_interrupts, enable_interrupts, - handler::trap_handler, - interrupt::enable_and_halt, - trap_frame::{TrapFrame, init_trap_frame}, + RUN_QUEUE_BITMAP, + arch::{ + helpers::current_cpu_core, + traps::{ + disable_interrupts, enable_interrupts, + handler::trap_handler, + interrupt::enable_and_halt, + trap_frame::{TrapFrame, init_trap_frame}, + }, }, config::TICK_SAFETY_DURATION, kprint, @@ -49,40 +53,44 @@ fn task_testing_sleep() -> ! { let current_tick = get_tick(); let mut trap_frame = TrapFrame::init(); unsafe { trap_handler(mepc, 0, cause, 0, 0, &mut trap_frame) }; + let core: usize = current_cpu_core(); // Blocked queue should have been updated, checking it #[allow(static_mut_refs)] - let blocked_queue = unsafe { &BLOCKED_QUEUE }; + let current_blocked_queue = unsafe { &mut BLOCKED_QUEUE[core] }; #[allow(static_mut_refs)] - let run_queue = unsafe { &RUN_QUEUE }; - if run_queue.size() != 0 { - test_failed!("The run queue should be empty, got: {}", run_queue.size()); + let current_run_queue = unsafe { &mut RUN_QUEUE[core] }; + if current_run_queue[1].size() != 0 { + test_failed!( + "The run queue should be empty, got: {}", + current_run_queue[1].size() + ); // Use infinite loop to make the CI crash from timeout. Can't return test failed from // here. loop {} } - if blocked_queue.size() != 1 { + if current_blocked_queue.get_count() != 1 { test_failed!( "The block queue should have 1 task in it, got: {}", - blocked_queue.size() + current_blocked_queue.get_count() ); // 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 { + if current_blocked_queue.get_count() != 0 { test_failed!( "The block queue should be empty, got: {}", - blocked_queue.size() + current_blocked_queue.get_count() ); // Use infinite loop to make the CI crash from timeout. Can't return test failed from // here. loop {} } - if run_queue.size() != 1 { + if current_run_queue[1].size() != 1 { test_failed!( "The run queue should have 1 task in it, got: {}", - run_queue.size() + current_run_queue[1].size() ); // Use infinite loop to make the CI crash from timeout. Can't return test failed from // here. @@ -119,7 +127,10 @@ fn test_task_primitives_sleep() -> u8 { unsafe { TASK_HANDLER = *task.as_mut().unwrap() }; #[allow(static_mut_refs)] unsafe { - RUN_QUEUE.push(3); + // Access the queue and bitmap from CPU core 0 + // run queue priority 1 + RUN_QUEUE[0][1].push(3); + RUN_QUEUE_BITMAP[0].set_bit(1); } task_context_switch(task.unwrap()); 0 From c5a3934874898ab12973fe957c7948c2acab1278 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 19 Feb 2026 10:12:48 +0100 Subject: [PATCH 75/84] feat(scheduler & tests): update the scheduler info header and add empty test module --- src/scheduler/mod.rs | 5 +++-- src/tests/mod.rs | 1 + src/tests/scheduler/mod.rs | 0 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 src/tests/scheduler/mod.rs diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index 8343eea..49e00e8 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -1,15 +1,16 @@ /* File info: Scheduler main file -Test coverage: ... +Test coverage: 0 Tested: Not tested: -Reasons: Not even really implemented so there's no need to test something that doesn't even consider finish +Reasons: Test framework don't handle correctly trap, so timer interrupts cannot work, hard to test a scheduler when a part of the kernel don't work in the test framework. Tests files: +- 'src/tests/scheduler/mod.rs' References: */ diff --git a/src/tests/mod.rs b/src/tests/mod.rs index a7f3dc9..66e4a18 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -11,6 +11,7 @@ mod ktime; mod mem; mod platform; mod primitives; +mod scheduler; mod suites; mod task; diff --git a/src/tests/scheduler/mod.rs b/src/tests/scheduler/mod.rs new file mode 100644 index 0000000..e69de29 From 410bfed8143ceefa2909b68808f48d390e3a1d81 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 19 Feb 2026 10:18:44 +0100 Subject: [PATCH 76/84] chore: remove all warnings --- src/main.rs | 1 - src/primitives/indexed_linked_list.rs | 10 +++++++--- src/scheduler/mod.rs | 4 ++-- src/task/primitives.rs | 21 +++++++-------------- 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/main.rs b/src/main.rs index 27b2c8e..33217fb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,7 +54,6 @@ pub mod scheduler; pub mod tests; // Use from modules -use arch::traps::misc::{read_mie, read_mstatus}; #[cfg(not(feature = "test"))] use core::panic::PanicInfo; use logs::LogLevel; diff --git a/src/primitives/indexed_linked_list.rs b/src/primitives/indexed_linked_list.rs index ffc88ba..55e054c 100644 --- a/src/primitives/indexed_linked_list.rs +++ b/src/primitives/indexed_linked_list.rs @@ -1,16 +1,20 @@ /* File info: IndexedLinkedList primitive type. -Test coverage: +Test coverage: 80 Tested: +- push +- pop +- get_head Not tested: Reasons: +- Lot of other methods are used in push, pop, and get_head, that why the test coverage is at 80 for me. Tests files: -- 'src/tests/primitives/delta_list.rs' +- 'src/tests/primitives/indexed_linked_list.rs' References: */ @@ -195,7 +199,7 @@ impl IndexedLinkedList { /// Take the node from the given index, replace it with None in the list. fn take_node(&mut self, idx: usize) -> Option { - let mut node = self.list[idx]; + let node = self.list[idx]; if node.is_some() { return self.list[idx].take(); } else { diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index 49e00e8..d1a67f1 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -22,7 +22,7 @@ use crate::{ scheduler::{SCHEDULER_CTX, SchedulerCtx, sched_ctx_restore}, }, config::{BLOCK_QUEUE_MAX_SIZE, CPU_CORE_NUMBER, RUN_QUEUE_MAX_SIZE, TASK_MAX_PRIORITY}, - kprint, kprint_fmt, log, + log, misc::{clear_reschedule, read_need_reschedule}, primitives::{bitmap::Bitmap, indexed_linked_list::IndexedLinkedList, ring_buff::RingBuffer}, task::{ @@ -91,7 +91,7 @@ pub fn scheduler() { } // Consider the `pid` as init, if wake_up_task.is_none(), we switch on the current task, so // we cannot reach this point unless wake_up_task is some and `pid` is set. - let mut task = task_list_get_task_by_pid(pid).expect("Failed to get the task by it's pid."); + let task = task_list_get_task_by_pid(pid).expect("Failed to get the task by it's pid."); let priority: u8 = task_priority(&task); task_awake_block_control(task); task.state = TaskState::Ready; diff --git a/src/task/primitives.rs b/src/task/primitives.rs index 18c45e3..f792205 100644 --- a/src/task/primitives.rs +++ b/src/task/primitives.rs @@ -19,19 +19,14 @@ Tests files: use crate::{ arch::{helpers::current_cpu_core, traps::interrupt::enable_and_halt}, - kprint, kprint_fmt, ktime::{set_ktime_ms, tick::get_tick}, log, logs::LogLevel, misc::need_reschedule, - scheduler::{BLOCKED_QUEUE, RUN_QUEUE, scheduler}, + scheduler::{BLOCKED_QUEUE, scheduler}, }; -use super::{ - TASK_HANDLER, Task, TaskBlockControl, TaskState, - list::{task_list_get_task_by_pid, task_list_update_task_by_pid}, - task_pid, -}; +use super::{TASK_HANDLER, Task, TaskBlockControl, TaskState, list::task_list_get_task_by_pid}; unsafe extern "C" { // Put the current task to sleep until the number of tick given is passed @@ -90,8 +85,8 @@ pub fn task_awake_blocked(tick: usize) { return; } #[allow(static_mut_refs)] - let mut blocked_task = current_blocked_queue.get_head_node(); - let mut pid: u16; + let blocked_task = current_blocked_queue.get_head_node(); + let pid: u16; if blocked_task.is_none() { log!( LogLevel::Error, @@ -126,11 +121,9 @@ pub fn task_awake_blocked(tick: usize) { if tick >= awake_tick { // push to run queue #[allow(static_mut_refs)] - unsafe { - // Set the need reschedule flag, the scheduler will check the block queue to - // awake correctly the task. - need_reschedule(); - }; + // Set the need reschedule flag, the scheduler will check the block queue to + // awake correctly the task. + need_reschedule(); } else { return; } From 3bf1f348a8436a55f0ac530ad221391ba6ca8703 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 19 Feb 2026 10:23:34 +0100 Subject: [PATCH 77/84] chore: remove unused import and clean unused variable --- src/tests/arch/riscv32/traps/interrupt.rs | 4 ++-- src/tests/task/primitives.rs | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/tests/arch/riscv32/traps/interrupt.rs b/src/tests/arch/riscv32/traps/interrupt.rs index 9b9c431..f945af0 100644 --- a/src/tests/arch/riscv32/traps/interrupt.rs +++ b/src/tests/arch/riscv32/traps/interrupt.rs @@ -6,7 +6,7 @@ use crate::{ mtvec_switch_to_direct_mode, mtvec_switch_to_vectored_mode, read_mie_msie, read_mie_mtie, trap_entry, }, - trap_frame::{KERNEL_TRAP_FRAME, init_trap_frame}, + trap_frame::KERNEL_TRAP_FRAME, }, tests::{TEST_MANAGER, TestBehavior, TestCase, TestSuite, TestSuiteBehavior}, }; @@ -32,7 +32,7 @@ pub fn test_mtvec_set_vectored_mode() -> u8 { pub fn test_mtvec_trap_entry() -> u8 { let mtvec_trap_entry = mtvec_read_trap_entry(); - let trap_entry_addr = trap_entry as usize as u32; + let trap_entry_addr = trap_entry as *const () as usize as u32; mtvec_set_trap_entry(); let updated_mtvec_trap_entry = mtvec_read_trap_entry(); if mtvec_trap_entry != 0 { diff --git a/src/tests/task/primitives.rs b/src/tests/task/primitives.rs index 02c764d..446d6d5 100644 --- a/src/tests/task/primitives.rs +++ b/src/tests/task/primitives.rs @@ -3,20 +3,19 @@ use crate::{ arch::{ helpers::current_cpu_core, traps::{ - disable_interrupts, enable_interrupts, + enable_interrupts, handler::trap_handler, - interrupt::enable_and_halt, - trap_frame::{TrapFrame, init_trap_frame}, + trap_frame::TrapFrame, }, }, config::TICK_SAFETY_DURATION, kprint, - ktime::{set_ktime_ms, set_ktime_seconds, tick::get_tick}, + ktime::set_ktime_seconds, scheduler::{BLOCKED_QUEUE, RUN_QUEUE}, task::{ CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, - primitives::{delay, sleep, r#yield}, + primitives::{delay, sleep}, task_context_switch, task_create, }, test_failed, test_info, @@ -50,7 +49,6 @@ fn task_testing_sleep() -> ! { // 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) }; let core: usize = current_cpu_core(); From 196cd043b729c8d110162ba4d162fcc954659563 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 19 Feb 2026 10:23:59 +0100 Subject: [PATCH 78/84] feat(tests primitives): add missing IndexedLinkedList pop test --- src/tests/primitives/indexed_linked_list.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/tests/primitives/indexed_linked_list.rs b/src/tests/primitives/indexed_linked_list.rs index 68452e9..7559cd2 100644 --- a/src/tests/primitives/indexed_linked_list.rs +++ b/src/tests/primitives/indexed_linked_list.rs @@ -1,7 +1,6 @@ use crate::{ - kprint_fmt, primitives::indexed_linked_list::IndexedLinkedList, - test_failed, test_info, + test_failed, tests::{TEST_MANAGER, TestBehavior, TestCase, TestSuite, TestSuiteBehavior}, }; @@ -113,6 +112,11 @@ pub fn indexed_linked_list_primitive_test_suite() { test_indexed_linked_list_get_head_node, TestBehavior::Default, ), + TestCase::init( + "IndexedLinkedList pop", + test_indexed_linked_list_pop, + TestBehavior::Default, + ), ], name: "IndexedLinkedList primitive type", behavior: TestSuiteBehavior::Default, From 4e59f262835c58fa53d247845ca4077e43c164dc Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 19 Feb 2026 10:26:26 +0100 Subject: [PATCH 79/84] chore: format file and added scheduler test suite empty fn --- src/tests/primitives/mod.rs | 1 - src/tests/scheduler/mod.rs | 1 + src/tests/task/primitives.rs | 6 +----- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/tests/primitives/mod.rs b/src/tests/primitives/mod.rs index 77a26d1..fc5dea1 100644 --- a/src/tests/primitives/mod.rs +++ b/src/tests/primitives/mod.rs @@ -1,3 +1,2 @@ pub mod indexed_linked_list; pub mod ring_buff; - diff --git a/src/tests/scheduler/mod.rs b/src/tests/scheduler/mod.rs index e69de29..f9d968b 100644 --- a/src/tests/scheduler/mod.rs +++ b/src/tests/scheduler/mod.rs @@ -0,0 +1 @@ +pub fn scheduler_test_suite() {} diff --git a/src/tests/task/primitives.rs b/src/tests/task/primitives.rs index 446d6d5..252152d 100644 --- a/src/tests/task/primitives.rs +++ b/src/tests/task/primitives.rs @@ -2,11 +2,7 @@ use crate::{ RUN_QUEUE_BITMAP, arch::{ helpers::current_cpu_core, - traps::{ - enable_interrupts, - handler::trap_handler, - trap_frame::TrapFrame, - }, + traps::{enable_interrupts, handler::trap_handler, trap_frame::TrapFrame}, }, config::TICK_SAFETY_DURATION, kprint, From e624955c6eff38e195d7474464de7bc442af05d3 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 19 Feb 2026 11:22:58 +0100 Subject: [PATCH 80/84] chore: refactor codebase for make check, improve codebase, format files, remove unused import and variable --- src/main.rs | 29 +-------- src/primitives/bitmap.rs | 1 + src/primitives/indexed_linked_list.rs | 71 +++++++++++++++------ src/scheduler/mod.rs | 18 ++++-- src/task/primitives.rs | 13 ++-- src/tests/arch/riscv32/task/task_context.rs | 3 +- src/tests/task/primitives.rs | 3 +- 7 files changed, 70 insertions(+), 68 deletions(-) diff --git a/src/main.rs b/src/main.rs index 33217fb..f01ba09 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ #![deny(clippy::expect_used)] #![deny(clippy::todo)] #![deny(clippy::unimplemented)] +#![feature(stmt_expr_attributes)] // Config module pub mod config; @@ -59,13 +60,8 @@ use core::panic::PanicInfo; use logs::LogLevel; use mem::mem_kernel_stack_info; -use scheduler::{RUN_QUEUE, RUN_QUEUE_BITMAP}; #[cfg(feature = "idle_task")] use task::task_idle_task; -use task::{ - TASK_HANDLER, list::task_list_get_task_by_pid, primitives::sleep, task_context_switch, - task_create, -}; #[unsafe(no_mangle)] unsafe extern "C" fn main() -> ! { @@ -80,16 +76,6 @@ unsafe extern "C" fn main() -> ! { log!(LogLevel::Info, "LrnRTOS started!"); #[cfg(feature = "idle_task")] task_idle_task(); - task_create("High priority", hight_priority_task, 10, 0x1000); - task_create("Low priority", low_priority_task, 4, 0x1000); - // High priority task. - let mut task = task_list_get_task_by_pid(1); - unsafe { TASK_HANDLER = *task.as_mut().unwrap() }; - unsafe { - RUN_QUEUE[0][4].push(2); - RUN_QUEUE_BITMAP[0].set_bit(4); - }; - task_context_switch(task.unwrap()); loop { log!(LogLevel::Debug, "Main loop."); unsafe { @@ -98,19 +84,6 @@ unsafe extern "C" fn main() -> ! { } } -fn hight_priority_task() -> ! { - loop { - print!("This is a high priority task !!!!!!!!!!\n"); - unsafe { sleep(10) }; - } -} - -fn low_priority_task() -> ! { - loop { - // print!("Low priority task\n"); - } -} - #[panic_handler] #[cfg(not(feature = "test"))] fn panic_handler(panic: &PanicInfo) -> ! { diff --git a/src/primitives/bitmap.rs b/src/primitives/bitmap.rs index 48e3829..6f4aa8e 100644 --- a/src/primitives/bitmap.rs +++ b/src/primitives/bitmap.rs @@ -4,6 +4,7 @@ pub struct Bitmap { } impl Bitmap { + #[allow(clippy::new_without_default)] pub const fn new() -> Self { Bitmap { map: 0 } } diff --git a/src/primitives/indexed_linked_list.rs b/src/primitives/indexed_linked_list.rs index 55e054c..ed9a20e 100644 --- a/src/primitives/indexed_linked_list.rs +++ b/src/primitives/indexed_linked_list.rs @@ -31,6 +31,7 @@ pub struct IndexedLinkedList { } impl IndexedLinkedList { + #[allow(clippy::new_without_default)] pub const fn new() -> Self { IndexedLinkedList { list: [const { None }; N], @@ -68,9 +69,13 @@ impl IndexedLinkedList { { let mut current_node: usize = self.head; for _ in 0..self.list.len() { - let node = self + // Allow expect use, if we can't get the current node, the linked-list is wrong, + // want to fail-fast + #[allow(clippy::expect_used)] + let get_current_node = self .get_node(current_node) - .expect("Failed to get the asked node, linked list may be empty or corrupted."); + .expect("Failed to get the asked node, linked-list may be empty or corrupted."); + let node = get_current_node; if node.id == id { log!( LogLevel::Warn, @@ -79,13 +84,11 @@ impl IndexedLinkedList { ); return; } else { - if node.next_node.is_none() { - break; - } else { - current_node = node - .next_node - .expect("Failed to get the next_node index behind the Option<>"); + if let Some(next_node) = node.next_node { + current_node = next_node; continue; + } else { + break; } } } @@ -111,6 +114,8 @@ impl IndexedLinkedList { }; let mut prev_node_ptr: Option = None; for _ in 0..self.list.len() { + // Allow expect, if we can't get the wanted node, the list may be corrupted. + #[allow(clippy::expect_used)] let node: &mut IndexedLinkedListNode = self .get_node(current_node) .expect("Failed to get the asked node, linked list may be empty or corrupted"); @@ -120,12 +125,21 @@ impl IndexedLinkedList { if node.next_node.is_none() { node.next_node = available_index; // Update current node in list - self.tail = available_index.expect("Failed to get the usize behind the Option<>. Maybe there's isn't available space in the delta-list."); + // Allow expect, if available index is None, maybe there's no available space + // in the linked-list, and we shouldn't reach this point. + #[allow(clippy::expect_used)] + let check_available_index = available_index.expect("Failed to get the usize behind the Option<>. Maybe there's isn't available space in the linked-list."); + self.tail = check_available_index; // Push new node to available index in list + // Allow unwrap, we check available index before + #[allow(clippy::unwrap_used)] self.list[available_index.unwrap()] = Some(new_node); break; } prev_node_ptr = Some(current_node); + // Allow expect, we check the next node before, if it's None, something wrong, we + // want to fail-fast + #[allow(clippy::expect_used)] let node_next_node = node .next_node .expect("Failed to get the next_node behind the Option<>"); @@ -140,8 +154,11 @@ impl IndexedLinkedList { // Get the previous head let prev_head = self.head; // Update the head to point to the new node - self.head = available_index + // Allow expect, the available index should not be None + #[allow(clippy::expect_used)] + let check_available_index = available_index .expect("Failed to get the available_index behind the Option<>"); + self.head = check_available_index; // Update the new_node to point to the old head new_node.next_node = Some(prev_head); // Update list to push new_node to head @@ -151,12 +168,17 @@ impl IndexedLinkedList { // If there's a previous node. new_node.next_node = Some(current_node); // Get the previous node + // Allow expect, if the previous_node index is not reachable or else, we want to + // fail-fast, the linked-list could be corrupted. + #[allow(clippy::expect_used)] let prev_node: &mut IndexedLinkedListNode = self .get_node(prev_node_ptr.expect("Failed to get the previous_node index behind Option<>, linked-list may be corrupted")) .expect("Failed to get the asked node, linked list may be empty or corrupted"); // Update previous node to point to the new node prev_node.next_node = available_index; // Push the new node to the list + // Allow expect, if the Available index is wrong, we want to fail fast + #[allow(clippy::expect_used)] self.list[available_index.expect("Available index should not be None")] = Some(new_node); break; @@ -170,14 +192,20 @@ impl IndexedLinkedList { pub fn pop(&mut self) -> Option { let head = self.head; let head_next_node = { - let head_node = self.get_node(head).expect("Failed to get the node."); - head_node.next_node + // If we can't get the head node, return None + let head_node = self.get_node(head); + // if head_node.is_none() { + // return None; + // } + head_node.as_ref()?; + // Allow unwrap, we check the value before + #[allow(clippy::unwrap_used)] + head_node.unwrap().next_node }; - if head_next_node.is_none() { - self.head = 0; + if let Some(next_node) = head_next_node { + self.head = next_node; } else { - self.head = head_next_node - .expect("Failed to get the usize behind the Option<> in node.next_node"); + self.head = 0; } self.count -= 1; // Get the head node @@ -191,9 +219,9 @@ impl IndexedLinkedList { pub fn get_node(&mut self, idx: usize) -> Option<&mut IndexedLinkedListNode> { let node = self.list[idx].as_mut(); if let Some(is_node) = node { - return Some(is_node); + Some(is_node) } else { - return None; + None } } @@ -201,9 +229,9 @@ impl IndexedLinkedList { fn take_node(&mut self, idx: usize) -> Option { let node = self.list[idx]; if node.is_some() { - return self.list[idx].take(); + self.list[idx].take() } else { - return None; + None } } @@ -215,7 +243,7 @@ impl IndexedLinkedList { break; } } - return output; + output } pub fn get_count(&self) -> usize { @@ -249,6 +277,7 @@ pub struct IndexedLinkedListNode { } impl IndexedLinkedListNode { + #[allow(clippy::new_without_default)] pub const fn new() -> Self { IndexedLinkedListNode { id: 0, diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index d1a67f1..68e1034 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -76,8 +76,9 @@ pub fn scheduler() { ); // Pop from blocked queue and move the task to the run queue let wake_up_task = current_blocked_queue.pop(); - let pid: u16; - if wake_up_task.is_none() { + let pid: u16 = if let Some(task) = wake_up_task { + task.id as u16 + } else { log!( LogLevel::Error, "Error getting the wake up task from blocked queue, blocked queue or need_reschedule flag can be corrupted." @@ -85,14 +86,15 @@ pub fn scheduler() { // Trigger a context switch on current task to avoid to fail-fast // TODO: return; - } else { - // Allow unwrap, we check the value before - pid = wake_up_task.unwrap().id as u16; - } + }; // Consider the `pid` as init, if wake_up_task.is_none(), we switch on the current task, so // we cannot reach this point unless wake_up_task is some and `pid` is set. + // Allow expect use, we check if we can't get the pid before. + // If we can't get the task with the pid, we wan't to fail-fast, because the pid and the + // task should be ok. + #[allow(clippy::expect_used)] let task = task_list_get_task_by_pid(pid).expect("Failed to get the task by it's pid."); - let priority: u8 = task_priority(&task); + let priority: u8 = task_priority(task); task_awake_block_control(task); task.state = TaskState::Ready; task_list_update_task_by_pid(pid, *task); @@ -105,6 +107,8 @@ pub fn scheduler() { if current_task.state == TaskState::Blocked { let pid = task_pid(¤t_task); let priority = task_priority(¤t_task); + // Allow expect, if we can't get the awake tick of the blocked task, we want to fail-fast + #[allow(clippy::expect_used)] let awake_tick = task_awake_tick(¤t_task).expect("Failed to get the task awake_tick"); // Push the current task to the blocked queue current_blocked_queue.push(pid as usize, awake_tick); diff --git a/src/task/primitives.rs b/src/task/primitives.rs index f792205..f821741 100644 --- a/src/task/primitives.rs +++ b/src/task/primitives.rs @@ -86,17 +86,16 @@ pub fn task_awake_blocked(tick: usize) { } #[allow(static_mut_refs)] let blocked_task = current_blocked_queue.get_head_node(); - let pid: u16; - if blocked_task.is_none() { + let pid: u16 = if let Some(task) = blocked_task { + task.id as u16 + } else { log!( LogLevel::Error, "Error getting the oldest task in run queue" ); + #[allow(clippy::needless_return)] return; - } else { - // Allow unwrap, we check the value before - pid = blocked_task.unwrap().id as u16; - } + }; // Allow expect, check the value before and if the pid become invalid we don't want to pursue // run time. #[allow(clippy::expect_used)] @@ -124,8 +123,6 @@ pub fn task_awake_blocked(tick: usize) { // Set the need reschedule flag, the scheduler will check the block queue to // awake correctly the task. need_reschedule(); - } else { - return; } } TaskBlockControl::None => (), diff --git a/src/tests/arch/riscv32/task/task_context.rs b/src/tests/arch/riscv32/task/task_context.rs index 2dd4e03..1c9cbac 100644 --- a/src/tests/arch/riscv32/task/task_context.rs +++ b/src/tests/arch/riscv32/task/task_context.rs @@ -3,9 +3,8 @@ use crate::scheduler::RUN_QUEUE; use core::{mem, ptr}; use crate::{ - RUN_QUEUE_BITMAP, arch::{scheduler::init_sched_ctx, task::task_context::TaskContext, traps::interrupt::halt}, - scheduler::scheduler, + scheduler::{RUN_QUEUE_BITMAP, scheduler}, task::{ CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, primitives::r#yield, task_context_switch, task_create, diff --git a/src/tests/task/primitives.rs b/src/tests/task/primitives.rs index 252152d..0dfe2d5 100644 --- a/src/tests/task/primitives.rs +++ b/src/tests/task/primitives.rs @@ -1,5 +1,4 @@ use crate::{ - RUN_QUEUE_BITMAP, arch::{ helpers::current_cpu_core, traps::{enable_interrupts, handler::trap_handler, trap_frame::TrapFrame}, @@ -7,7 +6,7 @@ use crate::{ config::TICK_SAFETY_DURATION, kprint, ktime::set_ktime_seconds, - scheduler::{BLOCKED_QUEUE, RUN_QUEUE}, + scheduler::{BLOCKED_QUEUE, RUN_QUEUE, RUN_QUEUE_BITMAP}, task::{ CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, From 78a7969386edae454360a8704482a5b5141d8a42 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 19 Feb 2026 11:23:17 +0100 Subject: [PATCH 81/84] feat(tests): add the scheduler_test_suite to test_suites fn --- src/tests/suites.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tests/suites.rs b/src/tests/suites.rs index 79989d7..df1519f 100644 --- a/src/tests/suites.rs +++ b/src/tests/suites.rs @@ -16,6 +16,7 @@ use super::{ platform::platform_test_suite, primitives::indexed_linked_list::indexed_linked_list_primitive_test_suite, primitives::ring_buff::ring_buff_primitive_test_suite, + scheduler::scheduler_test_suite, task::{list::task_list_test_suite, primitives::task_primitives_test_suite, task_test_suite}, }; @@ -38,4 +39,5 @@ pub fn test_suites() { task_test_suite(); task_context_test_suite(); task_primitives_test_suite(); + scheduler_test_suite(); } From 31ec2921b509a0ffa80598218da434deed703bd4 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 19 Feb 2026 11:23:43 +0100 Subject: [PATCH 82/84] feat(linkers): update test_mode linker, smelly I know --- linkers/linker_test_mode.ld | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/linkers/linker_test_mode.ld b/linkers/linker_test_mode.ld index ec770c3..16c830f 100644 --- a/linkers/linker_test_mode.ld +++ b/linkers/linker_test_mode.ld @@ -1,9 +1,9 @@ ENTRY(kstart) MEMORY { - RAM (rwx) : ORIGIN = 0x80200000, LENGTH = 128K + RAM (rwx) : ORIGIN = 0x80200000, LENGTH = 256K /* Not real ROM or flash from qemu virt machine, just use another RAM reg for now */ - ROM (rx) : ORIGIN = 0x80000000, LENGTH = 210K + ROM (rx) : ORIGIN = 0x80000000, LENGTH = 256K } SECTIONS { From 2ff0dcbd859f8a0479ff493addae9c2d28489ea3 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 19 Feb 2026 11:28:24 +0100 Subject: [PATCH 83/84] chore: bump kernel version to 0.4.5 --- 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 560ea25..b691e8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "lrnrtos" -version = "0.4.4" +version = "0.4.5" dependencies = [ "arrayvec", ] diff --git a/Cargo.toml b/Cargo.toml index a1efcbf..30b7052 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lrnrtos" -version = "0.4.4" +version = "0.4.5" edition = "2024" [[bin]] diff --git a/src/info.rs b/src/info.rs index adcd61f..f9b25c2 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.4"; +pub static KERNEL_VERSION: &str = "0.4.5"; From 5ca34ec9c4667ff2599faed432e87af3dba1d367 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 19 Feb 2026 11:32:43 +0100 Subject: [PATCH 84/84] chore: remove Debug impl in task_context structure and indexed_linked_list --- src/arch/riscv32/task/task_context.rs | 2 +- src/primitives/indexed_linked_list.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/arch/riscv32/task/task_context.rs b/src/arch/riscv32/task/task_context.rs index 9f4f9fa..3ba7a60 100644 --- a/src/arch/riscv32/task/task_context.rs +++ b/src/arch/riscv32/task/task_context.rs @@ -18,7 +18,7 @@ Tests files: use super::{restore_context, save_context}; #[repr(C)] -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone)] pub struct TaskContext { pub gpr: [u32; 32], // Offset 0 pub address_space: [u32; 2], // Offset 128 (first index 128; second index 132) diff --git a/src/primitives/indexed_linked_list.rs b/src/primitives/indexed_linked_list.rs index ed9a20e..d2c79ec 100644 --- a/src/primitives/indexed_linked_list.rs +++ b/src/primitives/indexed_linked_list.rs @@ -22,7 +22,7 @@ References: use crate::LogLevel; use crate::log; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub struct IndexedLinkedList { list: [Option; N], head: usize, @@ -267,7 +267,7 @@ impl IndexedLinkedList { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub struct IndexedLinkedListNode { pub id: usize, pub value: usize,