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 01/44] 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 02/44] 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 03/44] 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 04/44] 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 05/44] 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 06/44] 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 07/44] 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 08/44] 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 09/44] 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 10/44] 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 11/44] 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 12/44] 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 13/44] 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 14/44] 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 15/44] 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 16/44] 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 17/44] 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 18/44] 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 19/44] 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 20/44] 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 21/44] 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 22/44] 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 23/44] 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 24/44] 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 25/44] 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 26/44] 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 27/44] 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 28/44] 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 29/44] 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 30/44] 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 31/44] 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 32/44] 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 33/44] 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 34/44] 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 35/44] 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 36/44] 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 37/44] 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 38/44] 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 39/44] 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 40/44] 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 41/44] 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 42/44] 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 43/44] 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 44/44] 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,