From c6facc1a951ce9a70d1b402b34fd36f1290d0322 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 5 Feb 2026 10:53:27 +0100 Subject: [PATCH 01/16] feat: update the RUN_QUEUE init, add the idle task, and update main to test Change the BUFFER static to RUN_QUEUE static using a config static for max size, add a basic idle task, static, no change possible for the dev calling it, and update the main function to create task and test. Need sleep primitive to continue on this branch --- src/config.rs | 5 +++++ src/main.rs | 26 +++++++++++++++++++++----- src/scheduler/mod.rs | 28 +++++++++++++++++++++------- src/task/list.rs | 20 ++++++++++++++++++++ src/task/mod.rs | 23 ++++++++++++++++++++++- 5 files changed, 89 insertions(+), 13 deletions(-) diff --git a/src/config.rs b/src/config.rs index 2400fe5..70f2492 100644 --- a/src/config.rs +++ b/src/config.rs @@ -35,6 +35,11 @@ pub static FDT_MAX_PROPS: usize = 128; // ————————————— Define the max size of Task list ————————————— // ———————————————————————————————————————————————————————————— pub static TASK_LIST_MAX_SIZE: usize = 4; +// ———————————————————————————————————————————————————————————— +// ————————————— Define the max size of the Run queue ————————— +// ———————————————————————————————————————————————————————————— +// The run queue is len - 1, if the size is 4, it will only use 3 slot in the queue. +pub static RUN_QUEUE_MAX_SIZE: usize = 3; // Kernel stack size // WARNING diff --git a/src/main.rs b/src/main.rs index 7486484..ea72c99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,16 +54,16 @@ pub mod scheduler; pub mod tests; // Use from modules +use arch::task::r#yield; #[cfg(not(feature = "test"))] use core::panic::PanicInfo; use logs::LogLevel; use mem::mem_kernel_stack_info; -use primitives::ring_buff::RingBuffer; -// Static buffer to use as a ready queue for task. -pub static mut BUFFER: RingBuffer = RingBuffer::init(); -// Queue containing all blocked task. -pub static mut BLOCKED_QUEUE: RingBuffer = RingBuffer::init(); +use task::{ + CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, task_context_switch, + task_create, task_idle_task, +}; #[unsafe(no_mangle)] unsafe extern "C" fn main() -> ! { @@ -76,6 +76,15 @@ unsafe extern "C" fn main() -> ! { kernel_stack.bottom ); log!(LogLevel::Info, "LrnRTOS started!"); + task_idle_task(); + task_create("Test task", test_task, 1, 0x200); + #[allow(static_mut_refs)] + unsafe { + CURRENT_TASK_PID = 2 + }; + let mut task = task_list_get_task_by_pid(unsafe { CURRENT_TASK_PID }); + unsafe { TASK_HANDLER = *task.as_mut().unwrap() }; + task_context_switch(task.unwrap()); loop { log!(LogLevel::Debug, "Main loop uptime."); unsafe { @@ -84,6 +93,13 @@ unsafe extern "C" fn main() -> ! { } } +fn test_task() -> ! { + loop { + log!(LogLevel::Debug, "Test task, only yield."); + unsafe { r#yield() }; + } +} + #[panic_handler] #[cfg(not(feature = "test"))] fn panic_handler(panic: &PanicInfo) -> ! { diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index 9bd9589..a55841e 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -15,17 +15,23 @@ References: */ use crate::{ - BUFFER, arch::scheduler::{SCHEDULER_CTX, SchedulerCtx, sched_ctx_restore}, - log, + config::RUN_QUEUE_MAX_SIZE, + kprint_fmt, log, logs::LogLevel, + primitives::ring_buff::RingBuffer, task::{ TASK_HANDLER, TaskState, - list::{task_list_get_task_by_pid, task_list_update_task_by_pid}, + list::{task_list_get_idle_task, task_list_get_task_by_pid, task_list_update_task_by_pid}, task_context_switch, task_pid, }, }; +// Store all task Ready +pub static mut RUN_QUEUE: RingBuffer = RingBuffer::init(); +// Queue containing all blocked task. +pub static mut BLOCKED_QUEUE: RingBuffer = RingBuffer::init(); + /// Temporary function use to test the context switch and context restore on multiple task. /// Will certainly be used later on the real scheduler. /// Pop oldest task from RingBuffer, save the task context, update it, and repush it to the @@ -34,6 +40,9 @@ use crate::{ /// Not the best way to use the RingBuffer but it will do. #[unsafe(no_mangle)] pub fn dispatch() { + kprint_fmt!("debug run_queue buff entering dispatch: {:?}\n", unsafe { + RUN_QUEUE.buff + }); // Current running task let mut current_task = unsafe { *TASK_HANDLER }; if current_task.state != TaskState::Blocked { @@ -42,21 +51,26 @@ pub fn dispatch() { task_list_update_task_by_pid(pid, current_task); #[allow(static_mut_refs)] unsafe { - BUFFER.push(pid) + RUN_QUEUE.push(pid) }; } + // Update and load next task #[allow(static_mut_refs)] - let get_next_task = unsafe { BUFFER.pop() }; + let get_next_task = unsafe { RUN_QUEUE.pop() }; if get_next_task.is_none() { log!( - LogLevel::Error, - "Error getting the last task from RingBuffer" + LogLevel::Debug, + "No task available in the run queue, enter idle task." ); + let idle = task_list_get_idle_task(); + #[allow(clippy::expect_used)] + task_context_switch(idle.expect("ERROR: failed to get the idle task, invariant violated.")); } // Allow unwrap because it's a temporary function #[allow(clippy::unwrap_used)] let next_task_pid = get_next_task.unwrap(); + kprint_fmt!("debug pid: {}\n", next_task_pid); // Allow unwrap because it's a temporary function #[allow(clippy::unwrap_used)] let next_task = task_list_get_task_by_pid(next_task_pid).unwrap(); diff --git a/src/task/list.rs b/src/task/list.rs index 9219433..a874910 100644 --- a/src/task/list.rs +++ b/src/task/list.rs @@ -88,6 +88,18 @@ impl TaskList { pub fn get_last_pid(&self) -> u16 { self.last_pid } + + pub fn get_by_priority(&mut self, priority: u8) -> Option<&mut Task> { + for i in 0..TASK_LIST_MAX_SIZE { + let task = unsafe { (*self.list[i].get()).as_mut() }; + if let Some(is_task) = task + && is_task.priority == priority + { + return Some(is_task); + } + } + None + } } pub static mut TASK_LIST: TaskList = TaskList::init(); @@ -131,3 +143,11 @@ pub fn task_list_get_last_pid() -> u16 { TASK_LIST.get_last_pid() } } + +pub fn task_list_get_idle_task<'a>() -> Option<&'a mut Task> { + // Allow static mut refs for now, kernel only run in monocore + #[allow(static_mut_refs)] + unsafe { + TASK_LIST.get_by_priority(0) + } +} diff --git a/src/task/mod.rs b/src/task/mod.rs index 27b1bb5..93340bc 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -19,7 +19,12 @@ Tests files: use list::task_list_add_task; -use crate::{arch::task::task_context::TaskContext, log, logs::LogLevel, mem::mem_task_alloc}; +use crate::{ + arch::{task::task_context::TaskContext, traps::interrupt::enable_and_halt}, + log, + logs::LogLevel, + mem::mem_task_alloc, +}; pub mod list; pub mod primitives; @@ -170,3 +175,19 @@ pub fn task_context_save(task: &Task, ra: usize, sp: usize) { pub fn task_pid(task: &Task) -> u16 { task.pid } + +/// Create the idle task +pub fn task_idle_task() { + let task_name: &str = "Idle task"; + let func: fn() -> ! = idle_task_fn; + let priority: u8 = 0; + let size: usize = 0x100; + task_create(task_name, func, priority, size); +} + +fn idle_task_fn() -> ! { + loop { + log!(LogLevel::Debug, "Idle task."); + unsafe { enable_and_halt() }; + } +} From 78136051b01470b86438ddbcbab248a5e30c3f80 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Thu, 5 Feb 2026 10:54:16 +0100 Subject: [PATCH 02/16] feat: update the task_context test to use new RUN_QUEUE and enable the context_switch test --- src/tests/arch/riscv32/task/task_context.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tests/arch/riscv32/task/task_context.rs b/src/tests/arch/riscv32/task/task_context.rs index 33e3ad3..c9f5e46 100644 --- a/src/tests/arch/riscv32/task/task_context.rs +++ b/src/tests/arch/riscv32/task/task_context.rs @@ -1,4 +1,5 @@ -use crate::{BUFFER, print}; +use crate::print; +use crate::scheduler::RUN_QUEUE; use core::{mem, ptr}; use crate::{ @@ -124,7 +125,7 @@ pub fn test_task_context_switch() -> u8 { task_create("B", test_context_switch_b, 0, 0x1000); #[allow(static_mut_refs)] unsafe { - BUFFER.push(3) + RUN_QUEUE.push(3) }; unsafe { CURRENT_TASK_PID = 2 }; let mut task = task_list_get_task_by_pid(unsafe { CURRENT_TASK_PID }); @@ -153,7 +154,7 @@ pub fn task_context_test_suite() { TestCase::init( "Task context switch no invariants violated", test_task_context_switch, - TestBehavior::Skipped, + TestBehavior::Default, ), ], name: "RISC-V32 bit task context layout", From 3a9df137b3989bde190b5afdf6e4d26b503ae8bd Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 11:26:52 +0100 Subject: [PATCH 03/16] refactor: update codebase to use RUN_QUEUE instead of BUFFER --- src/main.rs | 13 ++++++++----- src/scheduler/mod.rs | 3 --- src/task/primitives.rs | 5 ++--- src/tests/task/primitives.rs | 6 +++--- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/main.rs b/src/main.rs index ea72c99..6646969 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,15 +54,16 @@ pub mod scheduler; pub mod tests; // Use from modules -use arch::task::r#yield; #[cfg(not(feature = "test"))] use core::panic::PanicInfo; use logs::LogLevel; use mem::mem_kernel_stack_info; use task::{ - CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, task_context_switch, - task_create, task_idle_task, + CURRENT_TASK_PID, TASK_HANDLER, + list::task_list_get_task_by_pid, + primitives::{sleep, r#yield}, + task_context_switch, task_create, task_idle_task, }; #[unsafe(no_mangle)] @@ -95,8 +96,10 @@ unsafe extern "C" fn main() -> ! { fn test_task() -> ! { loop { - log!(LogLevel::Debug, "Test task, only yield."); - unsafe { r#yield() }; + log!(LogLevel::Debug, "Test task, only sleep."); + unsafe { + sleep(20); + } } } diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index a55841e..136d616 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -40,9 +40,6 @@ pub static mut BLOCKED_QUEUE: RingBuffer = RingBuffer::init(); /// Not the best way to use the RingBuffer but it will do. #[unsafe(no_mangle)] pub fn dispatch() { - kprint_fmt!("debug run_queue buff entering dispatch: {:?}\n", unsafe { - RUN_QUEUE.buff - }); // Current running task let mut current_task = unsafe { *TASK_HANDLER }; if current_task.state != TaskState::Blocked { diff --git a/src/task/primitives.rs b/src/task/primitives.rs index abee205..0f3b336 100644 --- a/src/task/primitives.rs +++ b/src/task/primitives.rs @@ -18,12 +18,11 @@ Tests files: */ use crate::{ - BLOCKED_QUEUE, BUFFER, arch::traps::interrupt::enable_and_halt, ktime::{set_ktime_ms, tick::get_tick}, log, logs::LogLevel, - scheduler::dispatch, + scheduler::{BLOCKED_QUEUE, RUN_QUEUE, dispatch}, }; use super::{ @@ -118,7 +117,7 @@ pub fn task_awake_blocked(tick: usize) { // Allow expect, check the value before and if the pid become invalid we don't want to pursue // run time. #[allow(clippy::expect_used)] - BUFFER.push(pid.expect("Failed to get the pid behind the Option<>")); + RUN_QUEUE.push(pid.expect("Failed to get the pid behind the Option<>")); }; } else { // push to blocked queue diff --git a/src/tests/task/primitives.rs b/src/tests/task/primitives.rs index 33af7a0..aca0f93 100644 --- a/src/tests/task/primitives.rs +++ b/src/tests/task/primitives.rs @@ -1,5 +1,4 @@ use crate::{ - BLOCKED_QUEUE, BUFFER, arch::traps::{ disable_interrupts, enable_interrupts, handler::trap_handler, @@ -9,6 +8,7 @@ use crate::{ config::TICK_SAFETY_DURATION, kprint, ktime::{set_ktime_ms, set_ktime_seconds, tick::get_tick}, + scheduller::{BLOCKED_QUEUE, RUN_QUEUE}, task::{ CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, @@ -53,7 +53,7 @@ fn task_testing_sleep() -> ! { #[allow(static_mut_refs)] let blocked_queue = unsafe { &BLOCKED_QUEUE }; #[allow(static_mut_refs)] - let run_queue = unsafe { &BUFFER }; + let run_queue = unsafe { &RUN_QUEUE }; if run_queue.size() != 0 { test_failed!("The run queue should be empty, got: {}", run_queue.size()); // Use infinite loop to make the CI crash from timeout. Can't return test failed from @@ -119,7 +119,7 @@ fn test_task_primitives_sleep() -> u8 { unsafe { TASK_HANDLER = *task.as_mut().unwrap() }; #[allow(static_mut_refs)] unsafe { - BUFFER.push(3); + RUN_QUEUE.push(3); } task_context_switch(task.unwrap()); 0 From dad1a47c9d80909dc09dd6fe14c9a3eebd985794 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 11:52:27 +0100 Subject: [PATCH 04/16] chore: remove unused import --- src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6646969..5dbfabd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,9 +60,7 @@ use logs::LogLevel; use mem::mem_kernel_stack_info; use task::{ - CURRENT_TASK_PID, TASK_HANDLER, - list::task_list_get_task_by_pid, - primitives::{sleep, r#yield}, + CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, primitives::sleep, task_context_switch, task_create, task_idle_task, }; From d22794bff649fc4a8dc17607bd03a36c1385b074 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 15:15:45 +0100 Subject: [PATCH 05/16] feat: add a new CpusState structure and public API for it The CpusState structure will be used to store all useful flags per CPU core. Like scheduler flags, state flags, and more if needed. --- src/arch/riscv32/helpers.rs | 7 ++++ src/arch/riscv32/mod.rs | 1 + src/config.rs | 4 +++ src/misc.rs | 69 +++++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 src/arch/riscv32/helpers.rs diff --git a/src/arch/riscv32/helpers.rs b/src/arch/riscv32/helpers.rs new file mode 100644 index 0000000..8e3da4f --- /dev/null +++ b/src/arch/riscv32/helpers.rs @@ -0,0 +1,7 @@ +use crate::arch::asm; + +pub fn current_cpu_core() -> usize { + let id: usize = 0; + unsafe { asm!("csrr {}, mhartid", out(reg) id) }; + id +} diff --git a/src/arch/riscv32/mod.rs b/src/arch/riscv32/mod.rs index 65da303..405b2b1 100644 --- a/src/arch/riscv32/mod.rs +++ b/src/arch/riscv32/mod.rs @@ -4,3 +4,4 @@ pub mod scheduler; pub mod start; pub mod task; pub mod traps; +pub mod helpers; diff --git a/src/config.rs b/src/config.rs index 70f2492..bb2197c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -40,6 +40,10 @@ pub static TASK_LIST_MAX_SIZE: usize = 4; // ———————————————————————————————————————————————————————————— // The run queue is len - 1, if the size is 4, it will only use 3 slot in the queue. pub static RUN_QUEUE_MAX_SIZE: usize = 3; +// ———————————————————————————————————————————————————————————— +// ————————————— Define the number of CPU core ———————————————— +// ———————————————————————————————————————————————————————————— +pub static CPU_CORE_NUMBER: usize = 1; // Kernel stack size // WARNING diff --git a/src/misc.rs b/src/misc.rs index 3abefa1..badb99c 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -1,5 +1,74 @@ +use crate::{arch, config::CPU_CORE_NUMBER}; + #[repr(C)] pub struct RawTraitObject { pub data: *const (), pub vtable: *const (), } + +pub static mut CPUS_STATE: CpusState = CpusState::init(); + +#[repr(C)] +struct CpusState { + // Flags for the CPU states, not used yet. + cpu_state: [u8; CPU_CORE_NUMBER], + // Flags for the CPU scheduler state. + // bit 0: scheduler state, init or not. + // bit 1: need reschedule or not. + // bit 2:7: reschedule reason. + scheduler_state: [u8; CPU_CORE_NUMBER], +} + +impl CpusState { + const fn init() -> Self { + CpusState { + cpu_state: [0u8; CPU_CORE_NUMBER], + scheduler_state: [0u8; CPU_CORE_NUMBER], + } + } + + fn scheduler_set_reschedule_bit(&mut self, core: usize) { + let mut state = self.scheduler_state[core]; + let mask = 1 << 1; + // Set need reschedule bit. + state = state | mask; + } + + fn scheduler_clear_reschedule_bit(&mut self, core: usize) { + let mut state = self.scheduler_state[core]; + let mask = 0 << 1; + // Clear need reschedule bit. + state = state | mask; + } + + fn scheduler_read_reschedule_bit(&self, core: usize) -> bool { + let mut state = self.scheduler_state[core]; + let mask = 1 << 1; + // Get the bit 1 + state = state & mask; + } +} + +pub fn need_reschedule() { + let current_core = arch::helpers::current_cpu_core(); + #[allow(static_mut_refs)] + unsafe { + CPUS_STATE.scheduler_set_reschedule_bit(current_core) + }; +} + +pub fn clear_reschedule() { + let current_core = arch::helpers::current_cpu_core(); + #[allow(static_mut_refs)] + unsafe { + CPUS_STATE.scheduler_clear_reschedule_bit(current_core) + }; +} + +pub fn read_need_reschedule() { + let current_core = arch::helpers::current_cpu_core(); + #[allow(static_mut_refs)] + unsafe { + CPUS_STATE.scheduler_read_reschedule_bit(current_core) + }; +} From 9231cf5379a276b97b7ef54b9e937fafaa14b121 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 15:55:47 +0100 Subject: [PATCH 06/16] feat: update trap entry to handle reschedule, update method and public API for CPUS_STATE The reschedule work only once, then it won't work after that, weird but there's still improvement --- src/arch/riscv32/asm/trap_entry.S | 7 +++++++ src/arch/riscv32/helpers.rs | 4 ++-- src/misc.rs | 25 ++++++++++++++++++++++--- src/scheduler/mod.rs | 8 ++++++-- src/task/primitives.rs | 2 ++ 5 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/arch/riscv32/asm/trap_entry.S b/src/arch/riscv32/asm/trap_entry.S index bfddcb9..42d9249 100644 --- a/src/arch/riscv32/asm/trap_entry.S +++ b/src/arch/riscv32/asm/trap_entry.S @@ -50,6 +50,13 @@ trap_entry: load_gp %i .set i, i+1 .endr + # Check if a re-schedule is needed or not. + call read_need_reschedule + # If a0 != 0, goto 1f, else mret + bnez a0, 1f + mret +1: + call dispatch mret # Function used when the trap strack = 0, just infinite loop for debuging purpose diff --git a/src/arch/riscv32/helpers.rs b/src/arch/riscv32/helpers.rs index 8e3da4f..34db408 100644 --- a/src/arch/riscv32/helpers.rs +++ b/src/arch/riscv32/helpers.rs @@ -1,7 +1,7 @@ -use crate::arch::asm; +use core::arch::asm; pub fn current_cpu_core() -> usize { - let id: usize = 0; + let mut id: usize = 0; unsafe { asm!("csrr {}, mhartid", out(reg) id) }; id } diff --git a/src/misc.rs b/src/misc.rs index badb99c..8ad029b 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -27,11 +27,16 @@ impl CpusState { } } + fn read_scheduler_flag<'a>(&'a self, core: usize) -> &'a u8 { + &self.scheduler_state[core] + } + fn scheduler_set_reschedule_bit(&mut self, core: usize) { let mut state = self.scheduler_state[core]; let mask = 1 << 1; // Set need reschedule bit. state = state | mask; + self.scheduler_state[core] = state; } fn scheduler_clear_reschedule_bit(&mut self, core: usize) { @@ -39,13 +44,18 @@ impl CpusState { let mask = 0 << 1; // Clear need reschedule bit. state = state | mask; + self.scheduler_state[core] = state; } fn scheduler_read_reschedule_bit(&self, core: usize) -> bool { let mut state = self.scheduler_state[core]; - let mask = 1 << 1; // Get the bit 1 - state = state & mask; + let flag = (state >> 1) & 1; + if flag == 1 { + return true; + } else { + return false; + } } } @@ -65,10 +75,19 @@ pub fn clear_reschedule() { }; } -pub fn read_need_reschedule() { +#[unsafe(no_mangle)] +pub fn read_need_reschedule() -> bool { let current_core = arch::helpers::current_cpu_core(); #[allow(static_mut_refs)] unsafe { CPUS_STATE.scheduler_read_reschedule_bit(current_core) + } +} + +pub fn read_scheduler_flag<'a>() -> &'a u8 { + let current_core = arch::helpers::current_cpu_core(); + #[allow(static_mut_refs)] + unsafe { + return CPUS_STATE.read_scheduler_flag(current_core); }; } diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index 136d616..e53b675 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -19,6 +19,7 @@ use crate::{ config::RUN_QUEUE_MAX_SIZE, kprint_fmt, log, logs::LogLevel, + misc::{clear_reschedule, read_need_reschedule}, primitives::ring_buff::RingBuffer, task::{ TASK_HANDLER, TaskState, @@ -51,7 +52,11 @@ pub fn dispatch() { RUN_QUEUE.push(pid) }; } - + let resched = read_need_reschedule(); + if resched { + kprint_fmt!("debug\n"); + clear_reschedule(); + } // Update and load next task #[allow(static_mut_refs)] let get_next_task = unsafe { RUN_QUEUE.pop() }; @@ -67,7 +72,6 @@ pub fn dispatch() { // Allow unwrap because it's a temporary function #[allow(clippy::unwrap_used)] let next_task_pid = get_next_task.unwrap(); - kprint_fmt!("debug pid: {}\n", next_task_pid); // Allow unwrap because it's a temporary function #[allow(clippy::unwrap_used)] let next_task = task_list_get_task_by_pid(next_task_pid).unwrap(); diff --git a/src/task/primitives.rs b/src/task/primitives.rs index 0f3b336..fdbb991 100644 --- a/src/task/primitives.rs +++ b/src/task/primitives.rs @@ -22,6 +22,7 @@ use crate::{ ktime::{set_ktime_ms, tick::get_tick}, log, logs::LogLevel, + misc::need_reschedule, scheduler::{BLOCKED_QUEUE, RUN_QUEUE, dispatch}, }; @@ -118,6 +119,7 @@ pub fn task_awake_blocked(tick: usize) { // run time. #[allow(clippy::expect_used)] RUN_QUEUE.push(pid.expect("Failed to get the pid behind the Option<>")); + need_reschedule(); }; } else { // push to blocked queue From 027e5cdf58949f3d26ed7a8fa9fa73f5d23e834d Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Mon, 9 Feb 2026 16:04:19 +0100 Subject: [PATCH 07/16] feat: update the dispatch clear rechedule bit log to be a constant debug log, fix the clear need resched bit to use correct bitwise --- src/misc.rs | 2 +- src/scheduler/mod.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/misc.rs b/src/misc.rs index 8ad029b..9d263cd 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -43,7 +43,7 @@ impl CpusState { let mut state = self.scheduler_state[core]; let mask = 0 << 1; // Clear need reschedule bit. - state = state | mask; + state = state & mask; self.scheduler_state[core] = state; } diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index e53b675..b34a43a 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -54,7 +54,10 @@ pub fn dispatch() { } let resched = read_need_reschedule(); if resched { - kprint_fmt!("debug\n"); + log!( + LogLevel::Debug, + "Reschedule needed, clearing the need reschedule bit." + ); clear_reschedule(); } // Update and load next task From 935b8807eeb2afda8af65df36b1507305700bd28 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 09:21:42 +0100 Subject: [PATCH 08/16] feat: update the task context to save and restore mstatus Trying to fix the bug on the idle task, after some time running task and idle task it crash. Don't know why --- src/arch/riscv32/asm/context_offset.S | 5 +++-- src/arch/riscv32/asm/restore_context.S | 3 +++ src/arch/riscv32/asm/save_context.S | 3 +++ src/arch/riscv32/task/task_context.rs | 6 ++++-- src/arch/riscv32/traps/interrupt.rs | 6 ++++++ 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/arch/riscv32/asm/context_offset.S b/src/arch/riscv32/asm/context_offset.S index 07c4a6b..fab291f 100644 --- a/src/arch/riscv32/asm/context_offset.S +++ b/src/arch/riscv32/asm/context_offset.S @@ -11,6 +11,7 @@ .set OFFSET_SP, 140 # Return address .set OFFSET_RA, 144 +.set OFFSET_MSTATUS, 148 # Optionnal flags, will be use later maybe -.set OFFSET_FLAGS, 148 -.set OFFSET_INSTRUCTION_REG, 151 +.set OFFSET_FLAGS, 152 +.set OFFSET_INSTRUCTION_REG, 155 diff --git a/src/arch/riscv32/asm/restore_context.S b/src/arch/riscv32/asm/restore_context.S index 4ae7603..61f7965 100644 --- a/src/arch/riscv32/asm/restore_context.S +++ b/src/arch/riscv32/asm/restore_context.S @@ -12,6 +12,9 @@ restore_context: # Restore sp from current task context structure lw t0, OFFSET_SP(t6) mv sp, t0 + ## Update mstatus + lw t0, OFFSET_MSTATUS(t6) + csrw mstatus, t0 # Restore current task context using GNU macro # GNU macros from `src/arch/riscv32/asm/gnu_macro.S` .set i, 1 diff --git a/src/arch/riscv32/asm/save_context.S b/src/arch/riscv32/asm/save_context.S index 97f0b54..de3d8e5 100644 --- a/src/arch/riscv32/asm/save_context.S +++ b/src/arch/riscv32/asm/save_context.S @@ -9,6 +9,9 @@ save_context: sw a1, OFFSET_RA(t6) # Store word from t0(sp) in context structure sw a2, OFFSET_SP(t6) + # Save mstatus + csrr t0, mstatus + sw t0, OFFSET_MSTATUS(t6) # Save current task context using GNU macro # GNU macros from `src/arch/riscv32/asm/gnu_macro.S` .set i, 1 diff --git a/src/arch/riscv32/task/task_context.rs b/src/arch/riscv32/task/task_context.rs index 09d4165..ff183ee 100644 --- a/src/arch/riscv32/task/task_context.rs +++ b/src/arch/riscv32/task/task_context.rs @@ -25,8 +25,9 @@ pub struct TaskContext { pub pc: u32, // Offset 136 pub sp: u32, // Offset 140 pub ra: u32, // Offset 144 - pub flags: [u8; 3], // Offset 148 (first index 144; second index 145, third index 146) - pub instruction_register: u8, // Offset 151 + pub mstatus: u32, // Offset 148 + pub flags: [u8; 3], // Offset 152 (first index 152; second index 153, third index 154) + pub instruction_register: u8, // Offset 155 } impl TaskContext { @@ -37,6 +38,7 @@ impl TaskContext { pc: func as usize as u32, sp: size[0] as u32, ra: func as usize as u32, + mstatus: 136, flags: [0u8; 3], instruction_register: 0, } diff --git a/src/arch/riscv32/traps/interrupt.rs b/src/arch/riscv32/traps/interrupt.rs index bac5507..3543b39 100644 --- a/src/arch/riscv32/traps/interrupt.rs +++ b/src/arch/riscv32/traps/interrupt.rs @@ -98,6 +98,12 @@ pub fn disable_mstatus_mie() { unsafe { asm!("csrrc zero, mstatus, {}", in(reg) MIE) }; } +pub fn read_mstatus() -> u32 { + let value: u32; + unsafe { asm!("csrr {}, mstatus", out(reg) value) }; + value +} + // Machine Trap-Vector CSR pub fn mtvec_switch_to_vectored_mode() { From 6964d435c8d5294613534fbd1d20e04a8aacef8d Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 09:33:23 +0100 Subject: [PATCH 09/16] feat: update default mstatus in task context to 8 --- src/arch/riscv32/task/task_context.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/arch/riscv32/task/task_context.rs b/src/arch/riscv32/task/task_context.rs index ff183ee..fa7f034 100644 --- a/src/arch/riscv32/task/task_context.rs +++ b/src/arch/riscv32/task/task_context.rs @@ -38,7 +38,8 @@ impl TaskContext { pc: func as usize as u32, sp: size[0] as u32, ra: func as usize as u32, - mstatus: 136, + // Set mstatus to 8 by default to enable mie + mstatus: 8, flags: [0u8; 3], instruction_register: 0, } From 2c958f3da3c18ffb95b99c594ba168c0fa269a1c Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 09:40:21 +0100 Subject: [PATCH 10/16] chore: remove unused import and format files --- src/misc.rs | 4 ++-- src/scheduler/mod.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/misc.rs b/src/misc.rs index 9d263cd..6540bed 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -6,7 +6,7 @@ pub struct RawTraitObject { pub vtable: *const (), } -pub static mut CPUS_STATE: CpusState = CpusState::init(); +static mut CPUS_STATE: CpusState = CpusState::init(); #[repr(C)] struct CpusState { @@ -48,7 +48,7 @@ impl CpusState { } fn scheduler_read_reschedule_bit(&self, core: usize) -> bool { - let mut state = self.scheduler_state[core]; + let state = self.scheduler_state[core]; // Get the bit 1 let flag = (state >> 1) & 1; if flag == 1 { diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index b34a43a..0f7ab73 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -15,10 +15,10 @@ References: */ use crate::{ + LogLevel, arch::scheduler::{SCHEDULER_CTX, SchedulerCtx, sched_ctx_restore}, config::RUN_QUEUE_MAX_SIZE, - kprint_fmt, log, - logs::LogLevel, + log, misc::{clear_reschedule, read_need_reschedule}, primitives::ring_buff::RingBuffer, task::{ From 1d198d94b23670b1d0441f8aa8787e7ca2bb51b4 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 09:57:02 +0100 Subject: [PATCH 11/16] feat: update task context test to use updated offset and fix wrong import on primitives task test The test for context switch doesn't work, updating task context with mstatus broke the test. The context switch still work; just tested it in main. But the test mode need refactor to use correctly trap handling --- src/tests/arch/riscv32/task/task_context.rs | 16 +++++++++++++--- src/tests/task/primitives.rs | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/tests/arch/riscv32/task/task_context.rs b/src/tests/arch/riscv32/task/task_context.rs index c9f5e46..597fedc 100644 --- a/src/tests/arch/riscv32/task/task_context.rs +++ b/src/tests/arch/riscv32/task/task_context.rs @@ -51,6 +51,12 @@ pub fn test_task_context_init() -> u8 { "Task context has been initialized with wrong SP, expect sp to be set to the hi address of the task address space" ); } + // Check mstatus + if task_context.mstatus != 8 { + panic!( + "Task context has been initialized with wrong mstatus, expect mstatus to be set to 8 to only enable mstatus.mie" + ); + } 0 } @@ -73,14 +79,18 @@ pub fn test_task_context_offset() -> u8 { } let ra_off = mem::offset_of!(TaskContext, ra); if ra_off != 144 { - panic!("Task context ra offset must be 144, got: {sp_off}"); + panic!("Task context ra offset must be 144, got: {ra_off}"); } + let mstatus_off = mem::offset_of!(TaskContext, mstatus); + if mstatus_off != 148 { + panic!("Task context mstatus offset must be 148, got: {mstatus_off}"); + } let flags_off = mem::offset_of!(TaskContext, flags); - if flags_off != 148 { + if flags_off != 152 { panic!("Task context flags offset must be 144, got: {flags_off}"); } let instruction_reg_off = mem::offset_of!(TaskContext, instruction_register); - if instruction_reg_off != 151 { + if instruction_reg_off != 155 { panic!("Task context instruction_register offset must be 147, got: {instruction_reg_off}"); }; 0 diff --git a/src/tests/task/primitives.rs b/src/tests/task/primitives.rs index aca0f93..c96605c 100644 --- a/src/tests/task/primitives.rs +++ b/src/tests/task/primitives.rs @@ -8,7 +8,7 @@ use crate::{ config::TICK_SAFETY_DURATION, kprint, ktime::{set_ktime_ms, set_ktime_seconds, tick::get_tick}, - scheduller::{BLOCKED_QUEUE, RUN_QUEUE}, + scheduler::{BLOCKED_QUEUE, RUN_QUEUE}, task::{ CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, From 3d5832741b24db63a9b51ae2b698f6e079cf4b6b Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 10:12:17 +0100 Subject: [PATCH 12/16] feat: remove the test for the idle task in main, put the cfg with idle_task feature on the idle task creation Added a idle_task feature for the idle task creation. Avoid to always have it enable. --- src/main.rs | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5dbfabd..ef99bda 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,10 +59,8 @@ use core::panic::PanicInfo; use logs::LogLevel; use mem::mem_kernel_stack_info; -use task::{ - CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, primitives::sleep, - task_context_switch, task_create, task_idle_task, -}; +#[cfg(feature = "idle_task")] +use task::task_idle_task; #[unsafe(no_mangle)] unsafe extern "C" fn main() -> ! { @@ -75,32 +73,16 @@ unsafe extern "C" fn main() -> ! { kernel_stack.bottom ); log!(LogLevel::Info, "LrnRTOS started!"); + #[cfg(feature = "idle_task")] task_idle_task(); - task_create("Test task", test_task, 1, 0x200); - #[allow(static_mut_refs)] - unsafe { - CURRENT_TASK_PID = 2 - }; - let mut task = task_list_get_task_by_pid(unsafe { CURRENT_TASK_PID }); - unsafe { TASK_HANDLER = *task.as_mut().unwrap() }; - task_context_switch(task.unwrap()); loop { - log!(LogLevel::Debug, "Main loop uptime."); + log!(LogLevel::Debug, "Main loop."); unsafe { arch::traps::interrupt::enable_and_halt(); } } } -fn test_task() -> ! { - loop { - log!(LogLevel::Debug, "Test task, only sleep."); - unsafe { - sleep(20); - } - } -} - #[panic_handler] #[cfg(not(feature = "test"))] fn panic_handler(panic: &PanicInfo) -> ! { From a27dcc757a9e317a9ede9ed1ecf61f26fbc60860 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 10:13:02 +0100 Subject: [PATCH 13/16] chore: bump kernel version to 0.4.4 Also update the Cargo.toml with comment on feature and add the idle_task feature --- Cargo.lock | 2 +- Cargo.toml | 7 ++++++- src/info.rs | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b360627..560ea25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "lrnrtos" -version = "0.4.3" +version = "0.4.4" dependencies = [ "arrayvec", ] diff --git a/Cargo.toml b/Cargo.toml index 7b29236..a1efcbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lrnrtos" -version = "0.4.3" +version = "0.4.4" edition = "2024" [[bin]] @@ -12,6 +12,11 @@ arrayvec = { version = "0.7.6", default-features = false } [features] default = ["logs", "kprint"] +# Enable kernel logs logs = [] +# Enable early print using statically define uart device kprint = [] +# Switch to test mode test = [] +# Enable the idle task. +idle_task = [] diff --git a/src/info.rs b/src/info.rs index 30cbdd0..adcd61f 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,4 +1,4 @@ // The kernel exposes build-time version information at runtime. // This information is immutable and reflects the exact binary currently running. // It is intended for debugging, logging, and diagnostic purposes. -pub static KERNEL_VERSION: &str = "0.4.3"; +pub static KERNEL_VERSION: &str = "0.4.4"; From 56b429930f7443f9e08ed9d3d330530379b3a064 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 10:22:04 +0100 Subject: [PATCH 14/16] docs(kernel): update the task documentation and add idle task section Update the definition of task with the structure, and add a small definition for the idle task --- Documentation/kernel/task.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Documentation/kernel/task.md b/Documentation/kernel/task.md index 6ce2749..71e4913 100644 --- a/Documentation/kernel/task.md +++ b/Documentation/kernel/task.md @@ -6,6 +6,7 @@ - [Purpose](#purpose) - [Structure](#structure) - [How task is store](#how-task-is-store) + - [Idle task](#idle-task) - [Invariants](#invariants) - [References](#references) @@ -31,11 +32,20 @@ enum TaskState { Terminated, } +pub enum TaskBlockControl { + // Store the awake tick for task awakening. + AwakeTick(usize), + // No reason for the task block + None, +} + #[repr(C)] struct Task { // Arch dependant context, don't handle this field in task, only use struct method when // interacting with it. context: TaskContext, + // Task block control, define the reason the task is blocked. + block_control: TaskBlockControl, // Fn ptr to task entry point, this must never return. // This will surely disappear func: fn() -> !, @@ -53,6 +63,8 @@ pub struct TaskContext { pub address_space: [u32; 2], pub pc: u32, pub sp: u32, + pub ra: u32, + pub mstatus: u32, pub flags: [u8; 3], pub instruction_register: u8, } @@ -76,6 +88,12 @@ pub struct TaskList { } ``` +## Idle task + +The idle task is used to ensure that the kernel as always at least one task able to run. +This task is created at the lowest priority to ensure it does not use any CPU time if there are higher priority application tasks in the run queue. +It is not possible to update the idle task, it's a static defined task. + ## Invariants - The task's function must never return. From ab62c06916153a3bc9e49cb8c6ab7531e1e17039 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 10:36:09 +0100 Subject: [PATCH 15/16] chore: format files and remove warnings --- src/arch/riscv32/mod.rs | 2 +- src/misc.rs | 16 ++++++---------- src/tests/arch/riscv32/task/task_context.rs | 2 +- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/arch/riscv32/mod.rs b/src/arch/riscv32/mod.rs index 405b2b1..f724b04 100644 --- a/src/arch/riscv32/mod.rs +++ b/src/arch/riscv32/mod.rs @@ -1,7 +1,7 @@ pub mod asm; +pub mod helpers; pub mod mem; pub mod scheduler; pub mod start; pub mod task; pub mod traps; -pub mod helpers; diff --git a/src/misc.rs b/src/misc.rs index 6540bed..45e750d 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -27,7 +27,7 @@ impl CpusState { } } - fn read_scheduler_flag<'a>(&'a self, core: usize) -> &'a u8 { + fn read_scheduler_flag(&self, core: usize) -> &u8 { &self.scheduler_state[core] } @@ -35,7 +35,7 @@ impl CpusState { let mut state = self.scheduler_state[core]; let mask = 1 << 1; // Set need reschedule bit. - state = state | mask; + state |= mask; self.scheduler_state[core] = state; } @@ -43,7 +43,7 @@ impl CpusState { let mut state = self.scheduler_state[core]; let mask = 0 << 1; // Clear need reschedule bit. - state = state & mask; + state &= mask; self.scheduler_state[core] = state; } @@ -51,11 +51,7 @@ impl CpusState { let state = self.scheduler_state[core]; // Get the bit 1 let flag = (state >> 1) & 1; - if flag == 1 { - return true; - } else { - return false; - } + flag == 1 } } @@ -88,6 +84,6 @@ pub fn read_scheduler_flag<'a>() -> &'a u8 { let current_core = arch::helpers::current_cpu_core(); #[allow(static_mut_refs)] unsafe { - return CPUS_STATE.read_scheduler_flag(current_core); - }; + CPUS_STATE.read_scheduler_flag(current_core) + } } diff --git a/src/tests/arch/riscv32/task/task_context.rs b/src/tests/arch/riscv32/task/task_context.rs index 597fedc..4149d43 100644 --- a/src/tests/arch/riscv32/task/task_context.rs +++ b/src/tests/arch/riscv32/task/task_context.rs @@ -84,7 +84,7 @@ pub fn test_task_context_offset() -> u8 { let mstatus_off = mem::offset_of!(TaskContext, mstatus); if mstatus_off != 148 { panic!("Task context mstatus offset must be 148, got: {mstatus_off}"); - } + } let flags_off = mem::offset_of!(TaskContext, flags); if flags_off != 152 { panic!("Task context flags offset must be 144, got: {flags_off}"); From 309492bf820b5e2c39965bec13dbd1b8be79d437 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 10 Feb 2026 10:42:41 +0100 Subject: [PATCH 16/16] feat: skipped the task context test and add the info header on misc file --- src/misc.rs | 16 ++++++++++++++++ src/tests/arch/riscv32/task/task_context.rs | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/misc.rs b/src/misc.rs index 45e750d..54461d9 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -1,3 +1,19 @@ +/* +File info: Miscellaneous + +Test coverage: ... + +Tested: + +Not tested: + +Reasons: +- Will be refactor and tested in the scheduler update task. + +Tests files: +- ... +*/ + use crate::{arch, config::CPU_CORE_NUMBER}; #[repr(C)] diff --git a/src/tests/arch/riscv32/task/task_context.rs b/src/tests/arch/riscv32/task/task_context.rs index 4149d43..e482183 100644 --- a/src/tests/arch/riscv32/task/task_context.rs +++ b/src/tests/arch/riscv32/task/task_context.rs @@ -164,7 +164,7 @@ pub fn task_context_test_suite() { TestCase::init( "Task context switch no invariants violated", test_task_context_switch, - TestBehavior::Default, + TestBehavior::Skipped, ), ], name: "RISC-V32 bit task context layout",