Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
c6facc1
feat: update the RUN_QUEUE init, add the idle task, and update main t…
LuernOutOfOrder Feb 5, 2026
7813605
feat: update the task_context test to use new RUN_QUEUE and enable th…
LuernOutOfOrder Feb 5, 2026
3a9df13
refactor: update codebase to use RUN_QUEUE instead of BUFFER
LuernOutOfOrder Feb 9, 2026
dad1a47
chore: remove unused import
LuernOutOfOrder Feb 9, 2026
d22794b
feat: add a new CpusState structure and public API for it
LuernOutOfOrder Feb 9, 2026
9231cf5
feat: update trap entry to handle reschedule, update method and publi…
LuernOutOfOrder Feb 9, 2026
027e5cd
feat: update the dispatch clear rechedule bit log to be a constant de…
LuernOutOfOrder Feb 9, 2026
935b880
feat: update the task context to save and restore mstatus
LuernOutOfOrder Feb 10, 2026
6964d43
feat: update default mstatus in task context to 8
LuernOutOfOrder Feb 10, 2026
2c958f3
chore: remove unused import and format files
LuernOutOfOrder Feb 10, 2026
1d198d9
feat: update task context test to use updated offset and fix wrong im…
LuernOutOfOrder Feb 10, 2026
3d58327
feat: remove the test for the idle task in main, put the cfg with idl…
LuernOutOfOrder Feb 10, 2026
a27dcc7
chore: bump kernel version to 0.4.4
LuernOutOfOrder Feb 10, 2026
56b4299
docs(kernel): update the task documentation and add idle task section
LuernOutOfOrder Feb 10, 2026
ab62c06
chore: format files and remove warnings
LuernOutOfOrder Feb 10, 2026
309492b
feat: skipped the task context test and add the info header on misc file
LuernOutOfOrder Feb 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "lrnrtos"
version = "0.4.3"
version = "0.4.4"
edition = "2024"

[[bin]]
Expand All @@ -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 = []
18 changes: 18 additions & 0 deletions Documentation/kernel/task.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- [Purpose](#purpose)
- [Structure](#structure)
- [How task is store](#how-task-is-store)
- [Idle task](#idle-task)
- [Invariants](#invariants)
- [References](#references)
<!--toc:end-->
Expand All @@ -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() -> !,
Expand All @@ -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,
}
Expand All @@ -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.
Expand Down
5 changes: 3 additions & 2 deletions src/arch/riscv32/asm/context_offset.S
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions src/arch/riscv32/asm/restore_context.S
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/arch/riscv32/asm/save_context.S
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions src/arch/riscv32/asm/trap_entry.S
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions src/arch/riscv32/helpers.rs
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions src/arch/riscv32/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod asm;
pub mod helpers;
pub mod mem;
pub mod scheduler;
pub mod start;
Expand Down
7 changes: 5 additions & 2 deletions src/arch/riscv32/task/task_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
}
Expand Down
6 changes: 6 additions & 0 deletions src/arch/riscv32/traps/interrupt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
9 changes: 9 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/info.rs
Original file line number Diff line number Diff line change
@@ -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";
11 changes: 5 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u16, 3> = RingBuffer::init();
// Queue containing all blocked task.
pub static mut BLOCKED_QUEUE: RingBuffer<u16, 3> = RingBuffer::init();
#[cfg(feature = "idle_task")]
use task::task_idle_task;

#[unsafe(no_mangle)]
unsafe extern "C" fn main() -> ! {
Expand All @@ -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();
}
Expand Down
100 changes: 100 additions & 0 deletions src/misc.rs
Original file line number Diff line number Diff line change
@@ -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)
}
}
32 changes: 25 additions & 7 deletions src/scheduler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u16, RUN_QUEUE_MAX_SIZE> = RingBuffer::init();
// Queue containing all blocked task.
pub static mut BLOCKED_QUEUE: RingBuffer<u16, 3> = 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
Expand All @@ -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)]
Expand Down
Loading