diff --git a/Cargo.lock b/Cargo.lock index fff8951..b360627 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "lrnrtos" -version = "0.4.2" +version = "0.4.3" dependencies = [ "arrayvec", ] diff --git a/Cargo.toml b/Cargo.toml index f3833d1..7b29236 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "lrnrtos" -version = "0.4.2" +version = "0.4.3" edition = "2024" [[bin]] -name= "lrnrtos" +name = "lrnrtos" test = false [dependencies] -arrayvec = {version = "0.7.6", default-features = false} +arrayvec = { version = "0.7.6", default-features = false } [features] default = ["logs", "kprint"] diff --git a/Documentation/kernel/data_structure.md b/Documentation/kernel/data_structure.md new file mode 100644 index 0000000..c306dd9 --- /dev/null +++ b/Documentation/kernel/data_structure.md @@ -0,0 +1,39 @@ +# Kernel data structure + + +- [Kernel data structure](#kernel-data-structure) + - [Description](#description) + - [RingBuffer](#ringbuffer) + - [Invariants](#invariants) + - [AlignedStack16](#alignedstack16) + - [Invariants](#invariants-1) + + +## Description + +The kernel as multiple data structure implementation inside the codebase, they are useful to store and manipulate data inside the kernel. +These are all the data structure implemented inside the kernel. + +### RingBuffer + +A simple Ring buffer, used as an FIFO. If the RingBuffer is full and you try to push anyways, it will be abort. +It's like a close RingBuffer, you can't push if it's full and you don't pop before. + +#### Invariants + +- Length is always in [0, capacity]. +- The length is len - 1; there's always an empty slot in the array. +- Head and tail always remain within the backing array bounds. +- Push is only valid when the buffer is not full; violating this is a logic error (abort). +- Pop is only valid when the buffer is not empty; violating this is a logic error (abort). + +### AlignedStack16 + +This is just a structure wrapping a buffer of bytes, but the structure use the `#[repr(align(16))]`. +This type is used when you need a stack on a buffer, and the `sp` must be aligned on `16 bytes`. + +#### Invariants + +- The backing storage is always 16-byte aligned. +- Any stack pointer derived from this type must remain 16-byte aligned at all call boundaries. +- This type provides a memory-layout guarantee only; it does not validate stack usage correctness. diff --git a/Documentation/kernel/primitives.md b/Documentation/kernel/primitives.md new file mode 100644 index 0000000..aaf7412 --- /dev/null +++ b/Documentation/kernel/primitives.md @@ -0,0 +1,78 @@ +# Kernel primitives types and functions + + +- [Kernel primitives types and functions](#kernel-primitives-types-and-functions) + - [Description](#description) + - [Primitive type](#primitive-type) + - [Description](#description-1) + - [Task primitive](#task-primitive) + - [Description](#description-2) + - [yield](#yield) + - [sleep](#sleep) + - [task_awake_blocked](#taskawakeblocked) + - [Invariants](#invariants) + + +## Description + +This document describes the kernel primitives. + +In the context of this kernel, a *primitive* is defined as a low-level construct that +**directly affects kernel state or execution context**. +If using a type or function can change global kernel behavior, scheduling state, +or execution flow, it is considered a primitive. + +Two categories of primitives are documented here: + +- **Primitive types**: low-level types whose correct usage is required to preserve + kernel invariants. These types are not mere data structures; they encode execution, + synchronization, or memory-layout guarantees that the kernel relies on. + Examples include synchronization objects such as mutexes or types enforcing + strict alignment or execution constraints. + +- **Task primitives**: execution control operations that may only be used from + task context. These primitives modify the scheduling or blocking state of the + current task and therefore have observable effects on global kernel execution. + +Pure data structures that do not alter kernel state or execution context are +documented separately and are not considered primitives, even if they are used +internally by primitive implementations. +You can find data structure type here: `Documentation/kernel/data_structure.md`. + +### Primitive type + +#### Description + +There are currently no primitive type implemented in the kernel. + +### Task primitive + +#### Description + +To handle task correctly, the kernel need some primitives but only used in a task context. +These type can't and must not be used anywhere else than inside a task. + +#### yield + +Used in cooperative scheduling, when a task use `yield`, it will save it's context, and call a re-schedule. +It is used when you want a task to let another task take the control. + +#### sleep + +Put the task using the `sleep` primitive function to sleep for the given time. +It blocked the task until the kernel `GLOBAL_TICK` is equal or superior to the current tick + the given tick. +You can consider the given tick as `1ms`. + +#### task_awake_blocked + +Awake the oldest blocked task if it can. +This primitive is called from a timer interrupt, and only from a timer interrupt. +The timer interrupt will give the primitive `task_awake_blocked` the current `GLOBAL_TICK`, after updating it from the current interrupt. +The primitive will get the `oldest blocked task`, from the `BLOCKED_QUEUE`, then it'll check the reason why this task is blocked, and awake it if possible. + +#### Invariants + +- Task primitives must only be called from task context. +- The scheduler must be initialized before any task primitive is used. +- Time-based primitives rely on a functional timer subsystem. +- Principal data structure such as `RUN_QUEUE` and `BLOCKED_QUEUE` must be initialized before any task primitive is used. diff --git a/Documentation/kernel/timing_helpers.md b/Documentation/kernel/timing_helpers.md new file mode 100644 index 0000000..2a207fd --- /dev/null +++ b/Documentation/kernel/timing_helpers.md @@ -0,0 +1,22 @@ +# Kernel timing helpers + + +- [Kernel timing helpers](#kernel-timing-helpers) + - [Description](#description) + - [delay](#delay) + - [Invariants](#invariants) + + +## Description + +The kernel sometimes need to use some helpers to handle or manage timing. Here's a list of some helpers to help with that. + +### delay + +Block the CPU for the given time, in `ms`. +This is not really recommended to use, it will not put the CPU to sleep, just waiting for the next timer interrupt. +If you need a task to wait or something else, prefer the use of `yield`. + +### Invariants + +- The scheduler must be initialized before any timing helpers is used. diff --git a/linkers/linker_test_mode.ld b/linkers/linker_test_mode.ld index 3663103..ec770c3 100644 --- a/linkers/linker_test_mode.ld +++ b/linkers/linker_test_mode.ld @@ -3,7 +3,7 @@ ENTRY(kstart) MEMORY { RAM (rwx) : ORIGIN = 0x80200000, LENGTH = 128K /* Not real ROM or flash from qemu virt machine, just use another RAM reg for now */ - ROM (rx) : ORIGIN = 0x80000000, LENGTH = 200K + ROM (rx) : ORIGIN = 0x80000000, LENGTH = 210K } SECTIONS { diff --git a/src/arch/riscv32/asm/mod.rs b/src/arch/riscv32/asm/mod.rs index 431ae31..3922002 100644 --- a/src/arch/riscv32/asm/mod.rs +++ b/src/arch/riscv32/asm/mod.rs @@ -10,6 +10,8 @@ global_asm! { include_str!("set_kernel_sp.S"), // Yield function for context switch and scheduling include_str!("yield.S"), + // Sleep function for task and scheduling + include_str!("sleep.S"), // Scheduler context switch include_str!("sched_context.S"), // All task context offsets diff --git a/src/arch/riscv32/asm/sleep.S b/src/arch/riscv32/asm/sleep.S new file mode 100644 index 0000000..8ebc97c --- /dev/null +++ b/src/arch/riscv32/asm/sleep.S @@ -0,0 +1,18 @@ +.global sleep +.type sleep, @function +sleep: + # Move tick to another reg, a0 is used in save_context + mv t5, a0 + # Get current task ptr + la t0, TASK_HANDLER # Address in RAM + lw t1, 0(t0) # Get the value behind the ref + mv a0, t1 + # Save current ra + mv a1, ra + # Save current sp + mv a2, sp + # Call the save context function + call save_context + # Once save_context return, call the task_set_wake_tick fn + mv a0, t5 + call task_set_wake_tick diff --git a/src/arch/riscv32/asm/trap_entry.S b/src/arch/riscv32/asm/trap_entry.S index 1deabe0..bfddcb9 100644 --- a/src/arch/riscv32/asm/trap_entry.S +++ b/src/arch/riscv32/asm/trap_entry.S @@ -38,8 +38,8 @@ trap_entry: csrr a2, mcause csrr a3, mhartid csrr a4, mstatus - # Move trap frame structure into a3 function argument register - mv a3, t6 + # Move trap frame structure into a5 function argument register + mv a5, t6 # Call trap_handler rust function call trap_handler # Load all register back to previous state diff --git a/src/arch/riscv32/task/mod.rs b/src/arch/riscv32/task/mod.rs index f227d84..b4916f1 100644 --- a/src/arch/riscv32/task/mod.rs +++ b/src/arch/riscv32/task/mod.rs @@ -2,8 +2,6 @@ pub mod task_context; // Asm function for task context switch unsafe extern "C" { - // Yield function for cooperative scheduling - pub fn r#yield(); // Restore the task context pub fn restore_context(context: usize); // Save the current task context diff --git a/src/arch/riscv32/traps/handler.rs b/src/arch/riscv32/traps/handler.rs index effb92b..edab33b 100644 --- a/src/arch/riscv32/traps/handler.rs +++ b/src/arch/riscv32/traps/handler.rs @@ -21,7 +21,11 @@ Tests files: use crate::{ config::TICK_DURATION, - ktime::{set_ktime_ms, tick::increment_tick}, + ktime::{ + set_ktime_ms, + tick::{get_tick, increment_tick}, + }, + task::primitives::task_awake_blocked, }; use super::trap_frame::TrapFrame; @@ -90,5 +94,7 @@ fn timer_interrupt(hart: usize) { if hart == 0 { increment_tick(); } + let tick = get_tick(); + task_awake_blocked(tick); set_ktime_ms(TICK_DURATION); } diff --git a/src/info.rs b/src/info.rs index d3760f2..30cbdd0 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,4 +1,4 @@ // The kernel exposes build-time version information at runtime. // This information is immutable and reflects the exact binary currently running. // It is intended for debugging, logging, and diagnostic purposes. -pub static KERNEL_VERSION: &str = "0.4.2"; +pub static KERNEL_VERSION: &str = "0.4.3"; diff --git a/src/main.rs b/src/main.rs index fe4f2a7..7486484 100644 --- a/src/main.rs +++ b/src/main.rs @@ -62,6 +62,8 @@ 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(); #[unsafe(no_mangle)] unsafe extern "C" fn main() -> ! { diff --git a/src/primitives/ring_buff.rs b/src/primitives/ring_buff.rs index aa93e73..b2b54c7 100644 --- a/src/primitives/ring_buff.rs +++ b/src/primitives/ring_buff.rs @@ -23,7 +23,6 @@ References: use crate::{log, logs::LogLevel}; -#[derive(Debug)] pub struct RingBuffer { buff: [Option; N], // Oldest element in the buffer @@ -34,7 +33,7 @@ pub struct RingBuffer { count: usize, } -impl RingBuffer { +impl RingBuffer { pub const fn init() -> Self { RingBuffer { buff: [None; N], diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs index d2c7d73..9bd9589 100644 --- a/src/scheduler/mod.rs +++ b/src/scheduler/mod.rs @@ -36,13 +36,15 @@ use crate::{ pub fn dispatch() { // Current running task let mut current_task = unsafe { *TASK_HANDLER }; - current_task.state = TaskState::Ready; - let pid = task_pid(¤t_task); - task_list_update_task_by_pid(pid, current_task); - #[allow(static_mut_refs)] - unsafe { - BUFFER.push(pid) - }; + if current_task.state != TaskState::Blocked { + current_task.state = TaskState::Ready; + let pid = task_pid(¤t_task); + task_list_update_task_by_pid(pid, current_task); + #[allow(static_mut_refs)] + unsafe { + BUFFER.push(pid) + }; + } // Update and load next task #[allow(static_mut_refs)] let get_next_task = unsafe { BUFFER.pop() }; diff --git a/src/task/mod.rs b/src/task/mod.rs index 230c779..27b1bb5 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -22,6 +22,7 @@ use list::task_list_add_task; use crate::{arch::task::task_context::TaskContext, log, logs::LogLevel, mem::mem_task_alloc}; pub mod list; +pub mod primitives; // Mutable static to keep track of the current task // Only relevant on a monocore CPU. @@ -36,21 +37,30 @@ pub static mut TASK_HANDLER: *mut Task = core::ptr::null_mut(); #[repr(u8)] // Allow unused for now because this issue doesn't need to handle all task state #[allow(unused)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq)] pub enum TaskState { New, Running, Ready, Waiting, + Blocked, Terminated, } +#[derive(Copy, Clone)] +pub enum TaskBlockControl { + AwakeTick(usize), + None, +} + #[derive(Copy, Clone)] #[repr(C)] pub struct Task { // Arch dependant context, don't handle this field in task, only use struct method when // interacting with it. pub context: TaskContext, + // Task block control, define the reason the task is blocked. + pub block_control: TaskBlockControl, // Fn ptr to task entry point, this must never return. pub func: fn() -> !, pid: u16, @@ -89,6 +99,7 @@ impl Task { mem_reg.expect("Error: failed to get the task memory region"), func, ), + block_control: TaskBlockControl::None, func, pid: 0, name: buf, diff --git a/src/task/primitives.rs b/src/task/primitives.rs new file mode 100644 index 0000000..abee205 --- /dev/null +++ b/src/task/primitives.rs @@ -0,0 +1,142 @@ +/* +File info: Task primitives. + +Test coverage: yield and sleep. + +Tested: +- yield with two task. +- sleep with invariants from run queue and blocked queue. + +Not tested: +- delay + +Reasons: +- delay is hard to test, for now we test it by just checking it manually. + +Tests files: +- 'src/tests/task/primitives.rs' +*/ + +use crate::{ + BLOCKED_QUEUE, BUFFER, + arch::traps::interrupt::enable_and_halt, + ktime::{set_ktime_ms, tick::get_tick}, + log, + logs::LogLevel, + scheduler::dispatch, +}; + +use super::{ + TASK_HANDLER, Task, TaskBlockControl, TaskState, + list::{task_list_get_task_by_pid, task_list_update_task_by_pid}, + task_pid, +}; + +unsafe extern "C" { + // Put the current task to sleep until the number of tick given is passed + // tick: the number of tick the task need to sleep. + pub fn sleep(tick: usize); + // Yield function for cooperative scheduling + pub fn r#yield(); +} + +// Use no mangle because this function is called from an asm function +// Called from sleep primitive +#[unsafe(no_mangle)] +fn task_set_wake_tick(tick: usize) { + let current_tick = get_tick(); + let awake_tick = current_tick + tick; + // Call task primitive to update current task state + task_block_until(awake_tick); + // Call a re-schedule + dispatch(); +} + +/// Block the current task until the given tick is reach. +pub fn task_block_until(tick: usize) { + let current_task: *mut Task = unsafe { TASK_HANDLER }; + if current_task.is_null() { + log!( + LogLevel::Error, + "Error getting the current task, invariant violated. Sleep couldn't be used outside of a task." + ); + // See how to handle this, what to return or something else. + } + let mut current_task_deref: Task = unsafe { *current_task }; + // Update task + // Update current state to block + current_task_deref.state = TaskState::Blocked; + // Update block control and pass the awake_tick to it + current_task_deref.block_control = TaskBlockControl::AwakeTick(tick); + // Update task and push pid to block queue + let pid = task_pid(¤t_task_deref); + task_list_update_task_by_pid(pid, current_task_deref); + #[allow(static_mut_refs)] + unsafe { + BLOCKED_QUEUE.push(pid); + } +} + +/// Pop the oldest element in the blocked queue and check if the task can be awake. If not, repush +/// it to the blocked queue +/// TODO: Use a better data structure than a RingBuffer for the blocked queue. +pub fn task_awake_blocked(tick: usize) { + #[allow(static_mut_refs)] + let size = unsafe { BLOCKED_QUEUE.size() }; + if size == 0 { + return; + } + #[allow(static_mut_refs)] + let pid = unsafe { BLOCKED_QUEUE.pop() }; + if pid.is_none() { + log!(LogLevel::Error, "Error getting the oldest pid in run queue"); + return; + } + // Allow expect, check the value before and if the pid become invalid we don't want to pursue + // run time. + #[allow(clippy::expect_used)] + let task = task_list_get_task_by_pid(pid.expect("Error getting the pid behind the Option<>")); + if task.is_none() { + log!( + LogLevel::Error, + "Error getting the task by pid, the task may not exist" + ); + return; + } + // Allow expected, we check the value before, if it's some, there's shouldn't be any problem by + // unwrapping it. + #[allow(clippy::expect_used)] + match task + .expect("Failed to get the task behind the Option<>. This shouldn't be possible") + .block_control + { + TaskBlockControl::AwakeTick(awake_tick) => { + if tick >= awake_tick { + // push to run queue + #[allow(static_mut_refs)] + unsafe { + // Allow expect, check the value before and if the pid become invalid we don't want to pursue + // run time. + #[allow(clippy::expect_used)] + BUFFER.push(pid.expect("Failed to get the pid behind the Option<>")); + }; + } 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<>")) + }; + } + } + TaskBlockControl::None => (), + } +} + +/// Interrupt all operation on the CPU for the given time. +pub fn delay(ms: usize) { + set_ktime_ms(ms as u64); + unsafe { enable_and_halt() }; +} diff --git a/src/tests/arch/riscv32/task/task_context.rs b/src/tests/arch/riscv32/task/task_context.rs index 6e63da1..33e3ad3 100644 --- a/src/tests/arch/riscv32/task/task_context.rs +++ b/src/tests/arch/riscv32/task/task_context.rs @@ -2,15 +2,11 @@ use crate::{BUFFER, print}; use core::{mem, ptr}; use crate::{ - arch::{ - scheduler::init_sched_ctx, - task::{task_context::TaskContext, r#yield}, - traps::interrupt::halt, - }, + arch::{scheduler::init_sched_ctx, task::task_context::TaskContext, traps::interrupt::halt}, scheduler::dispatch, task::{ - CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, task_context_switch, - task_create, + CURRENT_TASK_PID, TASK_HANDLER, list::task_list_get_task_by_pid, primitives::r#yield, + task_context_switch, task_create, }, test_failed, test_info, tests::{TEST_MANAGER, TestBehavior, TestCase, TestSuite, TestSuiteBehavior}, diff --git a/src/tests/arch/riscv32/traps/interrupt.rs b/src/tests/arch/riscv32/traps/interrupt.rs index c7dc1ef..9b9c431 100644 --- a/src/tests/arch/riscv32/traps/interrupt.rs +++ b/src/tests/arch/riscv32/traps/interrupt.rs @@ -45,8 +45,6 @@ pub fn test_mtvec_trap_entry() -> u8 { } pub fn test_mscratch_trap_frame() -> u8 { - // Init trap_frame and declare ptr to it - init_trap_frame(); #[allow(static_mut_refs)] // Ptr to KERNEL_TRAP_FRAME static let ptr = unsafe { &mut KERNEL_TRAP_FRAME } as *mut _ as u32; diff --git a/src/tests/suites.rs b/src/tests/suites.rs index 86fb8ad..58c5312 100644 --- a/src/tests/suites.rs +++ b/src/tests/suites.rs @@ -15,7 +15,7 @@ use super::{ mem::memory_test_suite, platform::platform_test_suite, primitives::ring_buff::ring_buff_primitive_test_suite, - task::{list::task_list_test_suite, task_test_suite}, + task::{list::task_list_test_suite, primitives::task_primitives_test_suite, task_test_suite}, }; // Call all test suite function to auto register all suites in test manager. @@ -35,4 +35,5 @@ pub fn test_suites() { task_list_test_suite(); task_test_suite(); task_context_test_suite(); + task_primitives_test_suite(); } diff --git a/src/tests/task/mod.rs b/src/tests/task/mod.rs index a11cbe5..a476400 100644 --- a/src/tests/task/mod.rs +++ b/src/tests/task/mod.rs @@ -5,6 +5,7 @@ use crate::{ }; pub mod list; +pub mod primitives; /// This function is only used to create task for testing purpose. /// This must never be used in other cases diff --git a/src/tests/task/primitives.rs b/src/tests/task/primitives.rs new file mode 100644 index 0000000..33af7a0 --- /dev/null +++ b/src/tests/task/primitives.rs @@ -0,0 +1,149 @@ +use crate::{ + BLOCKED_QUEUE, BUFFER, + arch::traps::{ + disable_interrupts, enable_interrupts, + handler::trap_handler, + interrupt::enable_and_halt, + trap_frame::{TrapFrame, init_trap_frame}, + }, + config::TICK_SAFETY_DURATION, + kprint, + ktime::{set_ktime_ms, set_ktime_seconds, tick::get_tick}, + task::{ + CURRENT_TASK_PID, TASK_HANDLER, + list::task_list_get_task_by_pid, + primitives::{delay, sleep, r#yield}, + task_context_switch, task_create, + }, + test_failed, test_info, + tests::{TEST_MANAGER, TestBehavior, TestCase, TestSuite, TestSuiteBehavior}, +}; +use core::ptr; + +fn task_fn() -> ! { + let mut i: usize = 0; + loop { + kprint!("delay\n"); + delay(1000); + if i >= 8 { + // Exit Qemu + unsafe { ptr::write_volatile(0x100000 as *mut u32, 0x5555) }; + } + i += 1; + } +} + +fn task_sleep_fn() -> ! { + loop { + unsafe { + sleep(2); + } + } +} + +fn task_testing_sleep() -> ! { + let cause: usize = 2147483655; + // Random mepc + // TODO: improve mepc security in trap handler + let mepc: usize = 125696; + let current_tick = get_tick(); + let mut trap_frame = TrapFrame::init(); + unsafe { trap_handler(mepc, 0, cause, 0, 0, &mut trap_frame) }; + // Blocked queue should have been updated, checking it + #[allow(static_mut_refs)] + let blocked_queue = unsafe { &BLOCKED_QUEUE }; + #[allow(static_mut_refs)] + let run_queue = unsafe { &BUFFER }; + if run_queue.size() != 0 { + test_failed!("The run queue should be empty, got: {}", run_queue.size()); + // Use infinite loop to make the CI crash from timeout. Can't return test failed from + // here. + loop {} + } + if blocked_queue.size() != 1 { + test_failed!( + "The block queue should have 1 task in it, got: {}", + blocked_queue.size() + ); + // Use infinite loop to make the CI crash from timeout. Can't return test failed from + // here. + loop {} + } + unsafe { trap_handler(mepc, 0, cause, 0, 0, &mut trap_frame) }; + if blocked_queue.size() != 0 { + test_failed!( + "The block queue should be empty, got: {}", + blocked_queue.size() + ); + // Use infinite loop to make the CI crash from timeout. Can't return test failed from + // here. + loop {} + } + if run_queue.size() != 1 { + test_failed!( + "The run queue should have 1 task in it, got: {}", + run_queue.size() + ); + // Use infinite loop to make the CI crash from timeout. Can't return test failed from + // here. + loop {} + } + test_info!("Invariant from sleep and blocked queue successfully respected. Exit qemu..."); + unsafe { ptr::write_volatile(0x100000 as *mut u32, 0x5555) }; + // Check with condition the invariant, blocked queue updated etc + // Recall trap handler, then check that the block queue is empty. + loop {} +} + +fn test_task_primitives_delay() -> u8 { + task_create("Test delay", task_fn, 1, 0x1000); + unsafe { CURRENT_TASK_PID = 2 }; + let mut task = task_list_get_task_by_pid(unsafe { CURRENT_TASK_PID }); + unsafe { TASK_HANDLER = *task.as_mut().unwrap() }; + test_info!( + "The next output should be the task 'Test delay' printing an integer. The final output should be: 'delay: '" + ); + set_ktime_seconds(TICK_SAFETY_DURATION); + enable_interrupts(); + task_context_switch(task.unwrap()); + 0 +} + +fn test_task_primitives_sleep() -> u8 { + // pid 2 + task_create("Test sleep", task_sleep_fn, 1, 0x1000); + // pid 3 + task_create("Test sleep invariants", task_testing_sleep, 1, 0x1000); + unsafe { CURRENT_TASK_PID = 2 }; + let mut task = task_list_get_task_by_pid(unsafe { CURRENT_TASK_PID }); + unsafe { TASK_HANDLER = *task.as_mut().unwrap() }; + #[allow(static_mut_refs)] + unsafe { + BUFFER.push(3); + } + task_context_switch(task.unwrap()); + 0 +} + +pub fn task_primitives_test_suite() { + const TASK_PRIMITIVES_TEST_SUITE: TestSuite = TestSuite { + tests: &[ + TestCase::init( + "Task primitive delay", + test_task_primitives_delay, + TestBehavior::Skipped, + ), + TestCase::init( + "Task primitive sleep", + test_task_primitives_sleep, + TestBehavior::Skipped, + ), + ], + name: "Task primitives", + behavior: TestSuiteBehavior::Default, + }; + #[allow(static_mut_refs)] + unsafe { + TEST_MANAGER.add_suite(&TASK_PRIMITIVES_TEST_SUITE) + }; +}