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/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. 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/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 new file mode 100644 index 0000000..34db408 --- /dev/null +++ b/src/arch/riscv32/helpers.rs @@ -0,0 +1,7 @@ +use core::arch::asm; + +pub fn current_cpu_core() -> usize { + let mut 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..f724b04 100644 --- a/src/arch/riscv32/mod.rs +++ b/src/arch/riscv32/mod.rs @@ -1,4 +1,5 @@ pub mod asm; +pub mod helpers; pub mod mem; pub mod scheduler; pub mod start; diff --git a/src/arch/riscv32/task/task_context.rs b/src/arch/riscv32/task/task_context.rs index 09d4165..fa7f034 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,8 @@ impl TaskContext { pc: func as usize as u32, sp: size[0] as u32, ra: func as usize as u32, + // Set mstatus to 8 by default to enable mie + mstatus: 8, 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() { diff --git a/src/config.rs b/src/config.rs index 2400fe5..bb2197c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -35,6 +35,15 @@ 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; +// ———————————————————————————————————————————————————————————— +// ————————————— Define the number of CPU core ———————————————— +// ———————————————————————————————————————————————————————————— +pub static CPU_CORE_NUMBER: usize = 1; // Kernel stack size // WARNING 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"; diff --git a/src/main.rs b/src/main.rs index 7486484..ef99bda 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,12 +58,9 @@ pub mod tests; 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(); +#[cfg(feature = "idle_task")] +use task::task_idle_task; #[unsafe(no_mangle)] unsafe extern "C" fn main() -> ! { @@ -76,8 +73,10 @@ unsafe extern "C" fn main() -> ! { kernel_stack.bottom ); log!(LogLevel::Info, "LrnRTOS started!"); + #[cfg(feature = "idle_task")] + task_idle_task(); loop { - log!(LogLevel::Debug, "Main loop uptime."); + log!(LogLevel::Debug, "Main loop."); unsafe { arch::traps::interrupt::enable_and_halt(); } diff --git a/src/misc.rs b/src/misc.rs index 3abefa1..54461d9 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -1,5 +1,105 @@ +/* +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)] pub struct RawTraitObject { pub data: *const (), pub vtable: *const (), } + +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 read_scheduler_flag(&self, core: usize) -> &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 |= mask; + self.scheduler_state[core] = state; + } + + 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 &= mask; + self.scheduler_state[core] = state; + } + + fn scheduler_read_reschedule_bit(&self, core: usize) -> bool { + let state = self.scheduler_state[core]; + // Get the bit 1 + let flag = (state >> 1) & 1; + flag == 1 + } +} + +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) + }; +} + +#[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 { + CPUS_STATE.read_scheduler_flag(current_core) + } +} diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index 9bd9589..0f7ab73 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -15,17 +15,24 @@ References: */ use crate::{ - BUFFER, + LogLevel, arch::scheduler::{SCHEDULER_CTX, SchedulerCtx, sched_ctx_restore}, + config::RUN_QUEUE_MAX_SIZE, log, - logs::LogLevel, + misc::{clear_reschedule, read_need_reschedule}, + 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 @@ -42,17 +49,28 @@ pub fn dispatch() { task_list_update_task_by_pid(pid, current_task); #[allow(static_mut_refs)] unsafe { - BUFFER.push(pid) + RUN_QUEUE.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 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)] 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() }; + } +} diff --git a/src/task/primitives.rs b/src/task/primitives.rs index abee205..fdbb991 100644 --- a/src/task/primitives.rs +++ b/src/task/primitives.rs @@ -18,12 +18,12 @@ 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, + misc::need_reschedule, + scheduler::{BLOCKED_QUEUE, RUN_QUEUE, dispatch}, }; use super::{ @@ -118,7 +118,8 @@ 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<>")); + need_reschedule(); }; } else { // push to blocked queue diff --git a/src/tests/arch/riscv32/task/task_context.rs b/src/tests/arch/riscv32/task/task_context.rs index 33e3ad3..e482183 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::{ @@ -50,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 } @@ -72,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 @@ -124,7 +135,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 }); diff --git a/src/tests/task/primitives.rs b/src/tests/task/primitives.rs index 33af7a0..c96605c 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}, + scheduler::{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